Linux will happily talk to almost any USB device you plug in. That is convenient on a laptop. On a homelab server, a kiosk, a jump host, or a laptop that travels through untrusted desks, it is also how you get keyboard injection, unexpected mass storage, network gadgets, and composite "flash drive + HID" surprises.
USBGuard is the practical fix: a userspace policy engine that decides which USB devices are authorized before the kernel lets them become full citizens of the system. Think firewall rules, but for USB.
This guide gets you from package install to a reviewed allowlist, day-to-day CLI workflow, IPC least privilege, and audit logs you can actually use.
Why plain USB authorization is a weak link
By default, many systems authorize new USB devices automatically. That means:
- A keyboard-looking gadget can inject keystrokes as soon as it enumerates
- A "thumb drive" can also present HID, CDC, or wireless interfaces
- Devices without useful serial numbers are hard to pin down later
- Laptop lock screens do not protect you from a physical USB implant while you are away for coffee
USBGuard does not replace disk encryption, Secure Boot, or physical access controls. It closes a different gap: device identity and interface policy at insertion time.
Official framing from the project: USBGuard implements USB device authorization policies (what is allowed) and method-of-use style constraints via rule attributes (how a matched device may look to the system).
What you need
| Piece | Notes |
|---|---|
| Root or sudo | Install packages, write /etc/usbguard/*, manage the service |
usbguard package |
Debian/Ubuntu, Fedora, RHEL-family, Arch all ship it |
| systemd (typical) | usbguard.service |
| Console or SSH access that does not depend on a USB keyboard you are about to block | Critical on bare metal |
Hard warning from upstream: generate a rules file for currently attached devices before the first start of the daemon. If you start USBGuard with an empty policy and ImplicitPolicyTarget=block, your USB keyboard and mouse can disappear mid-session.
Install USBGuard
Debian / Ubuntu:
sudo apt update
sudo apt install usbguard
Fedora / RHEL-family:
sudo dnf install usbguard
Arch:
sudo pacman -S usbguard
Do not enable or start the service yet.
Step 1 — Generate a baseline policy from live devices
With your normal keyboard, mouse, YubiKey, headset dongle, and any always-present devices already plugged in:
sudo usbguard generate-policy | sudo tee /etc/usbguard/rules.conf >/dev/null
sudo chmod 0600 /etc/usbguard/rules.conf
sudo chown root:root /etc/usbguard/rules.conf
Or the review-first workflow from the man page:
sudo usbguard generate-policy > /tmp/usbguard-rules.conf
${EDITOR:-vi} /tmp/usbguard-rules.conf
sudo install -m 0600 -o root -g root /tmp/usbguard-rules.conf /etc/usbguard/rules.conf
Useful generate-policy options (from usbguard(1)):
| Flag | Effect |
|---|---|
-p / --with-ports
|
Force via-port on all generated device rules |
-P / --no-ports-sn
|
Do not add via-port for devices lacking iSerial (weaker uniqueness) |
-t block / -t reject
|
Append an explicit catch-all target |
-H / --hash-only
|
Match primarily on USBGuard device hashes |
-X / --no-hashes
|
Skip hash attributes |
Default behavior is the sane one for most hosts: devices without a serial number get a via-port constraint so a look-alike device on another port does not automatically inherit trust.
Inspect what you just generated:
sudo sed -n '1,80p' /etc/usbguard/rules.conf
You should see allow lines with vendor:product IDs, names, serials, hashes, interface sets, and sometimes ports.
Step 2 — Harden the daemon config
Main file: /etc/usbguard/usbguard-daemon.conf
A practical server-oriented baseline:
# /etc/usbguard/usbguard-daemon.conf
RuleFile=/etc/usbguard/rules.conf
# Unknown devices stay unauthorized until policy says otherwise
ImplicitPolicyTarget=block
# Evaluate the ruleset for devices already present at daemon start
PresentDevicePolicy=apply-policy
# Controllers usually stay as-is; changing this aggressively can surprise you
PresentControllerPolicy=keep
# New insertions after start go through the ruleset
InsertedDevicePolicy=apply-policy
# Do NOT restore permissive controller defaults on shutdown
# (upstream documents a bypass risk if this is true and the daemon is killed)
RestoreControllerDeviceState=false
DeviceManagerBackend=uevent
# Prefer fine-grained IPC ACL files over wide-open group access
IPCAccessControlFiles=/etc/usbguard/IPCAccessControl.d/
# Audit every decision
AuditBackend=FileAudit
AuditFilePath=/var/log/usbguard/usbguard-audit.log
# Optional on shared or multi-admin hosts
HidePII=false
Key knobs explained:
-
ImplicitPolicyTarget:allow(permissive),block(deauthorize), orreject(remove the device node). Default in docs isblock. -
PresentDevicePolicy=apply-policy: do not blindly keep whatever was authorized before the daemon started; re-check against rules. -
RestoreControllerDeviceState=false: recommended. Upstream notes that restoring controller state on shutdown can reopen a path to the OS default (often permissive) after a local exploit or crash path. -
IPCAccessControlFiles: least-privilege control for who may list devices, change authorization, or edit policy over the local IPC socket.
Create the audit directory if your package did not:
sudo install -d -m 0750 -o root -g root /var/log/usbguard
Step 3 — Start safely, then verify
sudo systemctl enable --now usbguard.service
systemctl is-active usbguard.service
sudo usbguard list-devices
sudo usbguard list-rules | sed -n '1,40p'
Expected:
- Service is
active - Your baseline devices show as allowed
- Rules match what you installed under
/etc/usbguard/rules.conf
If a needed device is blocked:
sudo usbguard list-devices -b
# Note the leading numeric ID in the first column (USBGuard's internal id)
sudo usbguard allow-device -p 12
-p / --permanent appends a device-specific allow rule to the policy file. Without it, the decision is temporary for the current daemon lifetime.
Block or reject the same way:
sudo usbguard block-device -p 15
sudo usbguard reject-device 18
Watch live events while you plug something in:
sudo usbguard watch
Rule language you will actually use
Rules are evaluated top to bottom. First match wins. If nothing matches, ImplicitPolicyTarget applies.
Core shape:
target device_id attributes... [if condition...]
Targets:
-
allow— authorize -
block— deauthorize -
reject— remove from the system
Device ID forms:
-
1050:0407— exact vendor:product -
1050:*— any product from vendor -
*:*— any device
Allow only pure mass-storage sticks
From the official rule-language examples:
allow with-interface equals { 08:*:* }
Anything that is not exactly a mass-storage interface set falls through to the implicit block/reject.
Reject "flash drive + keyboard" and other composite red flags
Blacklisting alone is the wrong overall strategy, but rejecting known-bad composites on top of a default-deny policy is useful:
# Only pure USB mass storage
allow with-interface equals { 08:*:* }
# Mass storage + HID keyboard/boot interfaces
reject with-interface all-of { 08:*:* 03:00:* }
reject with-interface all-of { 08:*:* 03:01:* }
# Mass storage + wireless controller / CDC-ish companions
reject with-interface all-of { 08:*:* e0:*:* }
reject with-interface all-of { 08:*:* 02:*:* }
Interface triples are USB class:subclass:protocol (cc:ss:pp). 08 is mass storage; 03 is HID; 02 is communications; e0 is wireless controller. Official USB-IF class codes are the reference if you need more classes.
Pin a specific security key to a port
allow 1050:0407 name "Yubico YubiKey OTP+FIDO+CCID" serial "0123456789" via-port "1-2" hash "replace-with-real-hash"
reject via-port "1-2"
That pattern allows one known device on one port and rejects everything else on the same port.
One keyboard interface at a time
Official condition example — allow a keyboard-class interface only if none is already allowed:
allow with-interface one-of { 03:00:01 03:01:01 } if !allowed-matches(with-interface one-of { 03:00:01 03:01:01 })
That reduces the "second keyboard appears while the laptop is locked" class of attacks.
Permanent manual allow after visual inspection
# Temporary authorize while you inspect lsusb / usbguard output
sudo usbguard allow-device 27
# When satisfied, make it permanent
sudo usbguard allow-device -p 27
# Or append a crafted rule
sudo usbguard append-rule 'allow with-interface equals { 08:*:* }'
List and prune:
sudo usbguard list-rules
sudo usbguard remove-rule 4
Least-privilege IPC access (do not skip this)
The daemon exposes a local IPC interface. Upstream is explicit: do not leave the ACL unconfigured. An open IPC surface lets local users change device authorization and rewrite policy.
Prefer access-control files under IPCAccessControlFiles:
# Operator who may list/listen/modify devices, list policy, hear exceptions
sudo usbguard add-user ops \
--user \
--devices=modify,list,listen \
--policy=list \
--exceptions=listen
# Restart so ACL changes take effect
sudo systemctl restart usbguard.service
A generally safe operator ACL content (also documented upstream) looks like:
Devices=modify list listen
Policy=list
Exceptions=listen
Root still manages permanent policy edits. Random logged-in desktop users should not get Policy=modify unless you really mean it.
Legacy broad form (less ideal):
IPCAllowedUsers=root
IPCAllowedGroups=wheel
Day-2 operations checklist
- Before first enable on a host: generate policy with all required devices attached.
- After docking-station or KVM changes: regenerate or manually allow the new hub tree; hubs change port paths.
-
When onboarding a YubiKey or token: prefer serial + hash, optionally
via-portfor desk machines. -
When allowing storage: prefer
with-interface equals { 08:*:* }over blind vendor wildcards. - Read the audit log after incidents:
sudo tail -n 50 /var/log/usbguard/usbguard-audit.log
# or, if AuditBackend=LinuxAudit on your distro build:
sudo ausearch -m USER_AVC,USER_DEVICE 2>/dev/null | tail
journalctl -u usbguard.service -b --no-pager | tail -n 80
- Keep a recovery path: root console, IPMI/iLO/iDRAC, or SSH that does not depend on a USB keyboard you might block during experiments.
Lab verification (safe sequence)
Use a spare machine or a VM with USB passthrough if you can. Sequence:
# 1) Baseline
sudo usbguard list-devices
sudo usbguard list-rules
# 2) Insert an unknown thumb drive
sudo usbguard watch
# In another terminal:
sudo usbguard list-devices -b
# 3) Confirm it did not mount / did not get interfaces bound
lsblk
dmesg | tail -n 30
# 4) Permanent allow only after you accept the risk
sudo usbguard allow-device -p <id>
# 5) Confirm policy file grew a matching allow rule
sudo tail -n 5 /etc/usbguard/rules.conf
On systems where a desktop portal/applet is installed, an implicit block can surface a user prompt. On servers, expect silence plus audit lines — authorize deliberately with the CLI.
Common failure modes
| Symptom | Likely cause | Fix |
|---|---|---|
USB keyboard dies right after systemctl start usbguard
|
Empty/missing rules, or present devices not allowed | Stop service from a non-USB console; regenerate policy; start again |
| Dock peripherals flap after cable move | Port-specific rules (via-port) no longer match |
Regenerate policy on the docked layout, or loosen port constraints for that device class |
usbguard list-devices fails for your user |
IPC ACL |
usbguard add-user ... and restart daemon |
| Permanent allow did not stick | Forgot -p, or RuleFile not writable |
Use allow-device -p; check permissions on rules.conf (mode 0600, root-owned) |
| Everything is allowed |
ImplicitPolicyTarget=allow or catch-all allow rule |
Set block/reject and remove broad allow *:* lines |
How this fits with the rest of your hardening
USBGuard is complementary, not redundant:
- seccomp / Landlock / AppArmor constrain process behavior after compromise
- auditd records security-relevant syscalls and watches
-
systemd device policies /
PrivateDevices=shrink service device views - USBGuard decides whether a newly attached USB function is authorized at all
Use all of them where they fit. None of them replace keeping untrusted physical ports disabled in firmware when a machine should never grow new peripherals.
Minimal copy-paste server profile
sudo apt update && sudo apt install -y usbguard # or dnf/pacman equivalent
# Attach only the devices this host should permanently trust, then:
sudo sh -c 'usbguard generate-policy > /etc/usbguard/rules.conf'
sudo chmod 0600 /etc/usbguard/rules.conf
sudo tee /etc/usbguard/usbguard-daemon.conf >/dev/null <<'EOF'
RuleFile=/etc/usbguard/rules.conf
ImplicitPolicyTarget=block
PresentDevicePolicy=apply-policy
PresentControllerPolicy=keep
InsertedDevicePolicy=apply-policy
RestoreControllerDeviceState=false
DeviceManagerBackend=uevent
IPCAccessControlFiles=/etc/usbguard/IPCAccessControl.d/
AuditBackend=FileAudit
AuditFilePath=/var/log/usbguard/usbguard-audit.log
EOF
sudo install -d -m 0750 /var/log/usbguard
sudo systemctl enable --now usbguard.service
sudo usbguard list-devices
Then add operator IPC rights only for people who should authorize devices.
References
- USBGuard project site: https://usbguard.github.io/
- Configuration reference (
usbguard-daemon.conf): https://usbguard.github.io/documentation/configuration.html - Rule language and official policy examples: https://usbguard.github.io/documentation/rule-language.html
- Upstream README (first-start warning, enable flow): https://github.com/USBGuard/usbguard
- Debian man page
usbguard(1): https://manpages.debian.org/bookworm/usbguard/usbguard.1.en.html - Related man pages:
usbguard-daemon(8),usbguard-daemon.conf(5),usbguard-rules.conf(5) - USB-IF defined class codes (interface class bytes): https://www.usb.org/defined-class-codes
Physical ports are part of your attack surface. USBGuard turns "whatever got plugged in" into an explicit, auditable allowlist — and that is exactly the kind of boring control that still pays off years later.
Top comments (0)