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.
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
- 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.
- 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





