TechEarl

USBGuard: Block Rogue USB Devices on Linux

Use USBGuard to block USB devices on Linux: generate a policy from the hardware you trust, set the daemon to reject everything else, and allow new devices by hand.

Ishan Karunaratne⏱️ 9 min readUpdated
Share thisCopied
Use USBGuard to block USB devices on Linux: generate a whitelist policy from trusted hardware, set the daemon to reject the rest, and authorize new devices by hand.

To block USB devices on Linux, install USBGuard, snapshot the hardware you already trust into a policy, and set the daemon to reject anything not on that list:

bash
sudo apt install usbguard
sudo usbguard generate-policy > /etc/usbguard/rules.conf
sudo systemctl enable --now usbguard

That is the whole idea: USBGuard is a daemon that sits in front of the kernel's USB authorization, so a device that is plugged in but not allowed never gets to talk to the system. It is the practical defense against BadUSB-style attacks, where a stick that looks like storage registers itself as a keyboard and types commands the moment it is inserted. The kernel will happily trust a brand-new keyboard; USBGuard makes it ask permission first.

One warning before you run anything: generate the policy while your real keyboard and mouse are attached, and keep an SSH session open. If you lock the daemon down with no rule for the keyboard you are typing on, the next keypress goes nowhere. I have done this. It is a memorable way to learn the order of operations.

Generate a policy from what you already trust

generate-policy walks every USB device currently attached and writes an allow rule for each one. Run it on a known-good machine, with only the peripherals you want permanently trusted plugged in:

bash
sudo usbguard generate-policy

A single rule looks like this:

code
allow id 046d:c52b serial "" name "USB Receiver" hash "kEUw9..." \
  with-interface { 03:01:01 03:00:00 } via-port "1-1.2"

Read it left to right: allow is the target, id 046d:c52b is the vendor and product ID, and the with-interface set is what the device claims to be (03:xx:xx is the HID class, which is keyboards and mice). That interface list is the part that matters for BadUSB. If a "flash drive" later shows up advertising a HID interface it never had before, it stops matching its own allow rule and gets blocked.

Redirect the output into the rules file, then lock the permissions down, because that file is now your trust boundary:

bash
sudo usbguard generate-policy > /etc/usbguard/rules.conf
sudo chmod 0600 /etc/usbguard/rules.conf

Edit the file by hand if the generated rules are too strict. Pinning via-port ties a device to one physical port, which is great for a server that never changes shape and annoying on a laptop where you move the same dongle between ports. Drop the via-port and hash clauses if you want a rule to match a device class more loosely.

Set what happens to everything else

The default for unmatched devices is controlled by ImplicitPolicyTarget in /etc/usbguard/usbguard-daemon.conf:

code
ImplicitPolicyTarget=block

There are three targets, and the difference between two of them is subtle but important:

TargetWhat the device can doWhen to use it
allowFull access, the device works normally.Devices on your whitelist.
blockDevice stays connected but is deauthorized; no driver binds, no data flows.The sane default for unknown devices. You can authorize it later without unplugging.
rejectDevice is logically removed from the system entirely.When you want unknown hardware to vanish, not sit there pending.

block is almost always the right implicit default. A blocked device is still visible to usbguard list-devices, so you can inspect it and allow it on the spot. A rejected device is gone, which is stricter but means re-plugging to reconsider it. Set the implicit target to block, keep explicit reject rules for hardware classes you never want (a reject with-interface { 03:00:00 } line to refuse all new keyboards is a common hardening move on kiosks and servers).

Restart the daemon after editing the config:

bash
sudo systemctl restart usbguard

Allow a new device on demand

When you plug something new in under a locked-down policy, it is blocked. List what the daemon sees, then authorize by the ID it printed:

bash
sudo usbguard list-devices
code
1: allow id 1d6b:0002 serial "0000:00:14.0" name "xHCI Host Controller" ...
14: block id 0781:5583 serial "..." name "Ultra Fit" hash "jReWv..." \
    with-interface { 08:06:50 } via-port "1-3"

The leading number is the device's runtime ID for this session. Allow it once for now, or append a permanent rule:

bash
# allow just this session
sudo usbguard allow-device 14

# allow it and write a permanent rule into rules.conf
sudo usbguard allow-device 14 -p

block-device and reject-device take the same runtime ID if you change your mind. To watch devices appear and disappear live while you test, run usbguard watch in one terminal and plug things in from another; every authorization decision streams past as it happens.

For a desktop, the usbguard-applet-qt tray app (packaged separately) pops a dialog asking allow or block each time you insert something, which is far nicer than reading device IDs out of a terminal. That applet runs as your normal user, so it needs permission to talk to the daemon over its IPC socket. Grant it with usbguard add-user (or the IPCAllowedUsers/IPCAllowedGroups lines in usbguard-daemon.conf):

bash
sudo usbguard add-user $(whoami) --devices=modify,list --policy=list

That lets your login user authorize devices and read the device list without becoming root, while keeping policy edits read-only. On a headless server you skip the applet and stay on the CLI.

Worked example: rejecting a BadUSB keyboard injector

Here is the scenario USBGuard is built for. The implicit target is block, the policy trusts one specific keyboard by hash, and someone plugs in a rubber-ducky-style device that enumerates as a HID keyboard:

bash
# list everything the daemon currently sees, blocked devices included
sudo usbguard list-devices --blocked
code
22: block id 1b1c:1b3d serial "" name "Gaming Keyboard" \
    with-interface { 03:01:01 } via-port "1-4"

The 03:01:01 interface is the HID keyboard class, the same profile a keystroke-injection device claims. It is sitting at block. No keystrokes reach the system, because no driver was allowed to bind. The legitimate keyboard still works, since its allow rule matched on insert. The attack device gets to do exactly nothing until a human with root explicitly allows ID 22, which is the entire point. The window where a malicious device autotypes a payload before anyone notices is closed.

Caveats and where this does not help

USBGuard guards the USB layer; it is not full device control. A few honest limits:

  • It runs as root and trusts root. Anyone who is already root can flip the policy. USBGuard raises the bar against physical and supply-chain USB attacks, not against an attacker who already owns the box.
  • Authorize-before-you-lock-out. Apply a strict policy over SSH or with a second input device available. Locking out the keyboard you are typing on is the classic self-inflicted wound.
  • Packaging differs by distro. It is usbguard on Debian, Ubuntu, Fedora, and RHEL (Fedora and RHEL have shipped it for years), and in the AUR on Arch. The systemd unit is usbguard.service; older or non-systemd setups start usbguard-daemon directly. The Qt applet is a separate package where it exists at all.
  • It does not encrypt or scan. A device you allow gets normal access, including normal malware risk from its contents. USBGuard decides whether a device may connect, not whether its files are safe.

It pairs naturally with the rest of a hardened host. Once you have locked down who can log in (see the SSH and root-login pieces below), controlling what hardware can attach closes the physical-access gap that remote hardening leaves open.

See also

Sources

Authoritative references this article was fact-checked against.

TagsUSBGuardUSBLinuxSecurityBadUSBsystem-administrationEndpoint Security

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

How to Disable Root Login on Linux

Disable direct root login over SSH and on the console, lock the root password, and move everyone to a normal account plus sudo, without locking yourself out.