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:
sudo apt install usbguard
sudo usbguard generate-policy > /etc/usbguard/rules.conf
sudo systemctl enable --now usbguardThat 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:
sudo usbguard generate-policyA single rule looks like this:
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:
sudo usbguard generate-policy > /etc/usbguard/rules.conf
sudo chmod 0600 /etc/usbguard/rules.confEdit 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:
ImplicitPolicyTarget=block
There are three targets, and the difference between two of them is subtle but important:
| Target | What the device can do | When to use it |
|---|---|---|
allow | Full access, the device works normally. | Devices on your whitelist. |
block | Device stays connected but is deauthorized; no driver binds, no data flows. | The sane default for unknown devices. You can authorize it later without unplugging. |
reject | Device 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:
sudo systemctl restart usbguardAllow 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:
sudo usbguard list-devices1: 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:
# allow just this session
sudo usbguard allow-device 14
# allow it and write a permanent rule into rules.conf
sudo usbguard allow-device 14 -pblock-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):
sudo usbguard add-user $(whoami) --devices=modify,list --policy=listThat 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:
# list everything the daemon currently sees, blocked devices included
sudo usbguard list-devices --blocked22: 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
usbguardon Debian, Ubuntu, Fedora, and RHEL (Fedora and RHEL have shipped it for years), and in the AUR on Arch. The systemd unit isusbguard.service; older or non-systemd setups startusbguard-daemondirectly. The Qt applet is a separate package where it exists at all. - It does not encrypt or scan. A device you
allowgets normal access, including normal malware risk from its contents. USBGuard decides whether a device may connect, not whether its files are safe. If you care about data leaving on a stick, that is a job for full-disk encryption with LUKS and policy, not USBGuard.
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.
It can, if you apply a strict policy without a rule for the keyboard you are using. Always run usbguard generate-policy with your real keyboard and mouse attached, and keep an SSH session or a second input device available the first time you enable the daemon.
If you do lock yourself out, boot into a recovery shell or edit /etc/usbguard/rules.conf from another session to add the allow rule, then restart the service. See disabling direct root login for the related lockout-avoidance discipline.
A blocked device stays physically connected but deauthorized: no driver binds and no data flows, and it still shows up in usbguard list-devices so you can allow it later. A rejected device is logically removed from the system entirely, as if you unplugged it. Block is the better implicit default because it lets you authorize an unknown device without re-inserting it.
It addresses the core BadUSB problem, which is a device silently enumerating as a different class (a "flash drive" that registers a HID keyboard interface to type commands). USBGuard rules match on the device's interface set, so a device that does not match its allowed profile is blocked before any driver binds. It is not a complete answer to every hardware attack, but it closes the autotype-on-insert vector.
In /etc/usbguard/rules.conf by default, with daemon settings (including ImplicitPolicyTarget) in /etc/usbguard/usbguard-daemon.conf. Lock the rules file to mode 0600 since it defines your trust boundary. Rules added with usbguard allow-device -p are appended there permanently.
Most likely yes. It is packaged as usbguard on Debian, Ubuntu, Fedora, and RHEL, and available through the AUR on Arch. Install it with your package manager, then enable the systemd unit with systemctl enable --now usbguard. The graphical tray applet ships as a separate package on the distros that carry it.
Three things to check, in order. First, confirm the daemon is actually running: systemctl status usbguard. If it is not active, nothing is being enforced. Second, check that you restarted the daemon after editing usbguard-daemon.conf, since ImplicitPolicyTarget is read at startup, not on the fly. Third, the device may already match an allow rule: run usbguard list-rules and look for a broad rule (one with no via-port or hash clause) that the device satisfies. A device plugged in before the daemon started keeps whatever authorization the kernel gave it, so re-insert it after the daemon is up.
You can deny devices with udev ATTR{authorized}="0" rules, and for a single known device that is fine. USBGuard is the better tool for a policy: it has a real rule language that matches on interface class, hash, serial, and port, a default-deny implicit target, a daemon that logs every decision, and an interactive allow-on-insert flow. udev rules fire on events and are awkward for "trust this exact set, reject everything else." Use udev for a one-off block, USBGuard when USB control is part of your security posture.
See also
- Harden sshd: disable password authentication: lock down remote access with keys only, the network-side counterpart to controlling physical USB access.
- Disable direct root login on Linux: the same defense-in-depth mindset applied to the root account, and a good reminder of how to avoid locking yourself out.
- Encrypt a disk with LUKS and cryptsetup: the other half of physical-access defense: USBGuard controls what hardware attaches, LUKS protects the data if the box itself walks off.
- OpenSnitch: the network-side equivalent: control what apps phone out.
Sources
Authoritative references this article was fact-checked against.
- USBGuard documentation (usbguard.github.io)usbguard.github.io
- USBGuard source repository (GitHub)github.com
- usbguard-daemon(8) manual page (man7.org)man7.org
- USBGuard IPC access control and configuration (usbguard.github.io)usbguard.github.io





