Guides
fail2ban for OpenVPN
An OpenVPN server on UDP 1194 attracts scanners and password guessers. fail2ban's openvpn filter bans clients that fail the TLS handshake, the certificate check or username/password authentication. Most setups need two small tweaks to make it match.
To ban OpenVPN attackers with fail2ban, enable the [openvpn] jail with port = 1194 and protocol = udp, and point it at the log your server actually writes. Use the systemd journal (backend = systemd) with a journalmatch for your OpenVPN unit, or a log-append file. The stock openvpn filter bans on TLS Error: TLS handshake failed, VERIFY ERROR and TLS Auth Error.
[openvpn]
enabled = true
port = 1194
protocol = udp
backend = systemd
journalmatch = _SYSTEMD_UNIT=openvpn-server@server.service _SYSTEMD_UNIT=openvpn@server.service
filter = openvpn[_daemon="(?:ovpn-\S+|openvpn)"]
maxretry = 3The rest of this page explains each line, because the stock jail's defaults (port = 443, logpath = /var/log/syslog) fit almost nobody.
What the openvpn filter matches#
The filter was added in fail2ban 1.1.1. The packaged versions on Debian 12/13 and Ubuntu 24.04/26.04 (1.0.2 and 1.1.0) don't include it. Check first:
ls /etc/fail2ban/filter.d/openvpn.confIf it's missing, create the same rules as a local filter:
[INCLUDES]
before = common.conf
[Definition]
_daemon = ovpn-server\d*
failregex = ^%(__prefix_line)s<HOST>:\d{4,5} (?:TLS Auth Error:|VERIFY ERROR:|TLS Error: TLS handshake failed\b|SIGUSR1\[soft,connection-reset\] received\b)
^%(__prefix_line)sTLS Error: cannot locate HMAC in incoming packet from \[AF_INET\]\s*<HOST>:\d{4,5}
ignoreregex =It bans on these events:
| Log message | Meaning |
|---|---|
TLS Error: TLS handshake failed |
Client never completed the TLS handshake: scanners, wrong protocol, wrong ta.key |
VERIFY ERROR: depth=..., error=... |
Client certificate rejected (unknown CA, revoked, expired) |
TLS Auth Error: Auth Username/Password verification failed for peer |
Wrong username or password with auth-user-pass-verify or a PAM/LDAP plugin |
TLS Error: cannot locate HMAC in incoming packet from [AF_INET]... |
Packet without the right tls-auth HMAC |
SIGUSR1[soft,connection-reset] received |
TCP-mode client reset (bad packet lengths, probes) |
Example lines from a server on UDP:
Oct 25 18:36:10 vpn ovpn-server[901]: 203.0.113.42:51234 TLS Error: TLS handshake failed
Oct 25 18:36:12 vpn ovpn-server[901]: 198.51.100.7:40122 TLS Auth Error: Auth Username/Password verification failed for peerThe ovpn-server vs openvpn name trap#
The filter expects the syslog tag ovpn-server. That's what Debian's legacy unit openvpn@server.service produces: it starts OpenVPN with --daemon ovpn-%i, so a config named server.conf logs as ovpn-server.
The newer upstream unit, openvpn-server@server.service (config in /etc/openvpn/server/), doesn't use --daemon. Its journal lines are tagged openvpn, and the stock filter misses every one of them:
Oct 25 18:36:10 vpn openvpn[901]: 203.0.113.42:51234 TLS Error: TLS handshake failedThe fix is the filter parameter from the jail at the top: filter = openvpn[_daemon="(?:ovpn-\S+|openvpn)"] accepts both names, and also names like ovpn-office from openvpn@office.
Pick the log source#
OpenVPN can log to the journal, to syslog or to its own file. Point the jail at the one you use.
Without log or log-append in the server config, OpenVPN writes to stdout and the journal stores it. Use the journal backend and match your unit name:
[openvpn]
enabled = true
port = 1194
protocol = udp
backend = systemd
journalmatch = _SYSTEMD_UNIT=openvpn-server@server.service
filter = openvpn[_daemon="(?:ovpn-\S+|openvpn)"]Replace server with your config name (systemctl list-units 'openvpn*' shows it). Install python3-systemd on Debian/Ubuntu if fail2ban complains it can't use the systemd backend.
With log-append /var/log/openvpn/openvpn.log in the server config, read that file:
[openvpn]
enabled = true
port = 1194
protocol = udp
backend = auto
logpath = /var/log/openvpn/openvpn.logbackend = auto matters on Ubuntu 24.04, whose defaults-debian.conf sets backend = systemd for every jail. Lines in the file have no syslog tag, and the filter's prefix accepts that. One catch: the upstream openvpn-server@.service unit runs with --suppress-timestamps, which also strips timestamps from the log-append file. fail2ban needs a timestamp on each line, so with that unit use the journal instead.
On systems with rsyslog, OpenVPN's journal lines are also copied to /var/log/syslog (Debian/Ubuntu) or /var/log/messages (RHEL). That's the stock jail's default logpath. It works, but the jail then scans every syslog line on the host.
Don't point fail2ban at the status file (status /var/log/openvpn/openvpn-status.log). It's a table of connected clients that OpenVPN rewrites, not a log.
UDP port and protocol#
The stock [openvpn] jail uses port = 443 and the default protocol = tcp, which suits OpenVPN on TCP 443. For the usual UDP 1194 set both:
port = 1194
protocol = udpIf you get these wrong, fail2ban bans the address on a port and protocol OpenVPN isn't using, and the attacker carries on. If you run both UDP 1194 and TCP 443, list both protocols: protocol = tcp,udp with port = 1194,443. The nftables and iptables actions loop over each protocol. To block the attacker on every port, use banaction = %(banaction_allports)s in the jail.
Test the jail#
Check the filter against real data before relying on it:
# journal
sudo fail2ban-regex -m "_SYSTEMD_UNIT=openvpn-server@server.service" systemd-journal 'openvpn[_daemon="(?:ovpn-\S+|openvpn)"]'
# file
sudo fail2ban-regex /var/log/openvpn/openvpn.log openvpnLines: ... matched should be above zero if anyone has failed a handshake recently. On a busy public server, that happens within minutes. Then:
sudo fail2ban-client -t
sudo systemctl restart fail2ban
sudo fail2ban-client status openvpnInterpreting the regex output is covered on the fail2ban-regex page, and the general checklist is in fail2ban not banning.
How useful is this on OpenVPN?#
A few honest caveats:
tls-cryptalready hides the server. Withtls-crypt(ortls-crypt-v2), packets without the right key are dropped before any TLS work. Scanners mostly produce no matching log line at all, so the jail sees little. That's a good outcome, not a broken jail.TLS handshake failedalso hits your own users when a laptop roams between networks or a client has an outdatedta.key. Keepmaxretryat 3 or more and put office networks inignoreip.- UDP source addresses can be spoofed. A forged handshake can't complete, so
VERIFY ERRORandTLS Auth Error(which need a finished handshake) are the strongest signals. A purely handshake-based ban could, in theory, be triggered for an address the attacker doesn't own. - IPv6: if your server listens on IPv6 and logs addresses in another format (for example with an
[AF_INET6]prefix), the stock regex won't match them. Test with your own log lines.
For a modern alternative that doesn't answer unauthenticated packets at all, see WireGuard and fail2ban.
The openvpn filter reference#
The built-in filters this guide uses, exactly as fail2ban 1.1.1 ships them — with the log lines they match and a link to test them. Looking for something else? See SoftEther VPN, xrdp and the other network-service filters, or the full filter library.
openvpn OpenVPN
The openvpn filter bans clients that fail the TLS handshake or authentication on an OpenVPN server. File: /etc/fail2ban/filter.d/openvpn.conf; jail [openvpn]; log on Debian/Ubuntu: /var/log/syslog.
Log lines it bans (from fail2ban's test suite):
Apr 25 10:57:36 hostname ovpn-server[901]: 83.97.20.30:10107 SIGUSR1[soft,connection-reset] received, client-instance restartingApr 25 10:57:49 hostname ovpn-server[901]: 83.97.20.30:29148 SIGUSR1[soft,connection-reset] received, client-instance restartingApr 25 10:58:03 hostname ovpn-server[901]: 83.97.20.30:2495 SIGUSR1[soft,connection-reset] received, client-instance restarting
Show the failregex fail2ban compiles (2 lines)
^(?:\[\])?\s*(?:<[^.]+\.[^.]+>\s+)?(?:\S+\s+)?(?:kernel:\s?\[ *\d+\.\d+\]:?\s+)?(?:@vserver_\S+\s+)?(?:(?:(?:\[\d+\])?:\s+[\[\(]?ovpn-server\d*(?:\(\S+\))?[\]\)]?:?|[\[\(]?ovpn-server\d*(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)\s+)?(?:\[ID \d+ \S+\]\s+)?<HOST>:\d{4,5} (?:TLS Auth Error:|VERIFY ERROR:|TLS Error: TLS handshake failed\b|SIGUSR1\[soft,connection-reset\] received\b)
^(?:\[\])?\s*(?:<[^.]+\.[^.]+>\s+)?(?:\S+\s+)?(?:kernel:\s?\[ *\d+\.\d+\]:?\s+)?(?:@vserver_\S+\s+)?(?:(?:(?:\[\d+\])?:\s+[\[\(]?ovpn-server\d*(?:\(\S+\))?[\]\)]?:?|[\[\(]?ovpn-server\d*(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)\s+)?(?:\[ID \d+ \S+\]\s+)?TLS Error: cannot locate HMAC in incoming packet from \[AF_INET\]\s*<HOST>:\d{4,5}Test openvpn in the browser or on the server: sudo fail2ban-regex /var/log/syslog openvpn · source
FAQ#
Is there a fail2ban filter for OpenVPN?#
Yes, filter.d/openvpn.conf, added in fail2ban 1.1.1. It matches TLS Error: TLS handshake failed, VERIFY ERROR, TLS Auth Error and missing tls-auth HMAC lines. Older packaged versions (1.0.2, 1.1.0) don't include it, so copy its rules into /etc/fail2ban/filter.d/openvpn.local.
Why does my fail2ban openvpn jail not match anything?#
The filter expects the syslog tag ovpn-server, which comes from Debian's openvpn@server unit. The upstream openvpn-server@server unit logs as openvpn. Use filter = openvpn[_daemon="(?:ovpn-\S+|openvpn)"] and point the jail at the right journal unit or log file.
How do I configure the fail2ban openvpn jail for UDP 1194?#
Set port = 1194 and protocol = udp in the [openvpn] section of jail.local. The stock jail defaults to port 443 over TCP, so without those two lines bans land on the wrong port and protocol and the attacker can keep connecting.
Where does OpenVPN log failed connections?#
With systemd and no log/log-append option, in the journal: journalctl -u openvpn-server@server. With log-append, in the file you named, often /var/log/openvpn/openvpn.log. With rsyslog, also in /var/log/syslog. The status file isn't a log and doesn't work for fail2ban.
Should I use fail2ban with OpenVPN tls-crypt?#
It does no harm, but it has less to do. With tls-crypt, unauthenticated packets are dropped silently, so scanners rarely produce a log line. fail2ban still bans clients that hold the key but present a bad certificate or password, which is where it adds value.
Spotted something wrong or out of date? Tell us — every page is checked against the current fail2ban release.