Guides
fail2ban and WireGuard
People search for a WireGuard jail and find none. That's by design: WireGuard doesn't answer or log unauthenticated packets, so fail2ban has nothing to read. Here's what that means, the one workaround and its limits, and where fail2ban helps on a WireGuard server.
fail2ban can't protect WireGuard itself, and it doesn't need to. WireGuard silently drops every packet that isn't signed with a valid peer key: it sends no reply and writes no log line. Without failed-login lines there's nothing for a fail2ban filter to match, which is why fail2ban ships no wireguard filter or jail. Protect the services you reach through the tunnel, and SSH, instead.
Why WireGuard has nothing for fail2ban to ban#
fail2ban works by reading a log, finding repeated failures from one IP and banning it. That model fits SSH, mail and web logins, where an attacker can try passwords and the server logs each wrong one.
WireGuard has no passwords. Each peer is identified by a public key, and the first handshake packet must already carry a valid MAC computed from the server's public key. A scanner that doesn't know the key can't produce a valid packet. The interface:
- Doesn't respond. From outside, a WireGuard port looks like a closed or filtered UDP port. Port scanners can't even tell it's there.
- Doesn't log. The kernel module writes nothing for dropped packets at the default log level.
- Costs almost nothing per bad packet. Checking the MAC is cheap, so a flood of junk doesn't load the CPU the way TLS handshakes do on OpenVPN. Under real load WireGuard switches to cookie replies on its own.
So there's no brute-force surface to defend. Brute-forcing a 256-bit Curve25519 key isn't a realistic attack, and fail2ban couldn't see it anyway.
The kernel debug-log workaround (and why to skip it)#
The Linux WireGuard module has debug messages that aren't printed by default. You can switch them on with dynamic debug:
echo 'module wireguard +p' | sudo tee /sys/kernel/debug/dynamic_debug/controlThe kernel log then contains lines such as:
Oct 25 18:36:10 vpn kernel: wireguard: wg0: Invalid MAC of handshake, dropping packet from 203.0.113.42:51820
Oct 25 18:36:12 vpn kernel: wireguard: wg0: Invalid handshake initiation from 198.51.100.7:40122A custom filter for those lines works:
[Definition]
failregex = ^\s*(?:\S+ )?kernel: (?:\[\s*\d+\.\d+\]\s*)?wireguard: \S+: (?:Invalid MAC of handshake, dropping packet|Invalid handshake initiation) from \[?<ADDR>\]?:\d+$
ignoreregex =
journalmatch = _TRANSPORT=kernel[wireguard]
enabled = true
port = 51820
protocol = udp
backend = systemd
filter = wireguard
maxretry = 5The regex matches both line formats above, IPv4 and IPv6, in fail2ban-regex. It still isn't worth running in production:
- The messages are rate-limited. The kernel uses
net_ratelimit()for them, so under a flood most packets produce no line. fail2ban sees an unpredictable sample. - Debug mode is noisy and needs debugfs. It also logs every normal handshake and keepalive, it doesn't survive a reboot unless you add a boot parameter or a unit, and it needs a kernel built with
CONFIG_DYNAMIC_DEBUG. - UDP source addresses are trivially spoofed. An unauthenticated packet proves nothing about who sent it. Anyone can send junk with your office IP as the source and get it banned from your own VPN.
- There's nothing to protect. Those packets were already dropped at no cost. Banning their source changes nothing about WireGuard's security.
If you want to see who's probing the port, count packets in the firewall (nft counters or a log rule with a rate limit). That's information, not a reason to ban.
What to protect on a WireGuard server#
The useful fail2ban jails on a WireGuard box are the services around the tunnel:
- SSH on the public interface. If SSH is reachable from the internet at all, run the sshd jail. Better still, bind SSH to the WireGuard address only (
ListenAddress 10.0.0.1insshd_config) so it never appears on the public IP. - Web UIs exposed publicly. Management tools such as wg-easy, a router UI or a dashboard on the same host need their own login jail, or should only be reachable through the tunnel.
- Services behind the tunnel. Nextcloud, Home Assistant and similar apps should still have their jails, because a stolen laptop with a valid peer config can still try passwords. Their logs will show the client's tunnel address (
10.0.0.x), and fail2ban can ban that address. - Repeat offenders across jails. Enable recidive so an address that keeps getting banned elsewhere is blocked on all ports.
If the services behind the tunnel are on other machines, a ban on the WireGuard host's input chain doesn't reach them. Run fail2ban where the service runs, or ban in the forward path. The nftables guide covers the table and hook fail2ban uses.
Ban a misbehaving peer#
A peer with valid keys that abuses a service (password guessing on an internal app, scanning the LAN) shows up with its tunnel IP. A fail2ban ban on that tunnel IP works like any other ban. The stronger fix is to remove or rotate the peer:
sudo wg set wg0 peer <PUBLIC_KEY> removeThen delete the [Peer] block from /etc/wireguard/wg0.conf so it isn't restored on restart.
Alternatives if you want WireGuard-side controls#
- Restrict the listening port by source when your peers have stable IPs: an nftables or firewalld rule allowing UDP 51820 only from known networks.
- Rate-limit UDP to the port with an nftables
limitrule if junk traffic costs bandwidth. - Change the port. It doesn't add security, but it cuts the scan noise in your firewall logs.
- Use a different tool if you need reputation-based blocking of scanners at the network edge. See alternatives to fail2ban for options such as CrowdSec.
FAQ#
Does fail2ban work with WireGuard?#
Not for WireGuard itself. WireGuard drops packets that lack a valid key without replying or logging, so there's no failed-login line for fail2ban to match. Use fail2ban for SSH, web UIs and the services you reach through the tunnel, where password failures are logged.
Is there a fail2ban filter for WireGuard?#
No. fail2ban ships no wireguard filter or jail. You can build one from WireGuard's kernel debug messages, but they are rate-limited and need dynamic debug enabled. They are also triggered by unauthenticated UDP packets with spoofable source addresses, so banning on them is unreliable.
Can WireGuard be brute forced?#
Not in any practical sense. Peers authenticate with Curve25519 key pairs, and the server ignores any handshake without a valid MAC derived from its public key. There's no password to guess. The realistic risks are a stolen peer private key and weak logins on the services behind the tunnel.
How do I see failed WireGuard handshakes?#
At the default log level you can't, by design. wg show lists the latest handshake of each known peer. For packets from unknown sources, enable dynamic debug (echo 'module wireguard +p' > /sys/kernel/debug/dynamic_debug/control) temporarily and read journalctl -k, or count packets with an nftables rule.
Should I still run fail2ban on a WireGuard server?#
Yes, if anything else on it is reachable: SSH, a web UI or apps behind the tunnel. Bind SSH to the tunnel address if you can, enable the sshd and recidive jails, and add jails for each service with a login page.
Spotted something wrong or out of date? Tell us — every page is checked against the current fail2ban release.