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.

Updated 8 min readChecked against fail2ban 1.1.1

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.

/etc/fail2ban/jail.local
[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     = 3

The 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:

bash
ls /etc/fail2ban/filter.d/openvpn.conf

If it's missing, create the same rules as a local filter:

/etc/fail2ban/filter.d/openvpn.local
[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:

log
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 peer

The 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:

log
Oct 25 18:36:10 vpn openvpn[901]: 203.0.113.42:51234 TLS Error: TLS handshake failed

The 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:

/etc/fail2ban/jail.local
[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.

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:

ini
port     = 1194
protocol = udp

If 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:

bash
# 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 openvpn

Lines: ... matched should be above zero if anyone has failed a handshake recently. On a busy public server, that happens within minutes. Then:

bash
sudo fail2ban-client -t
sudo systemctl restart fail2ban
sudo fail2ban-client status openvpn

Interpreting 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-crypt already hides the server. With tls-crypt (or tls-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 failed also hits your own users when a laptop roams between networks or a client has an outdated ta.key. Keep maxretry at 3 or more and put office networks in ignoreip.
  • UDP source addresses can be spoofed. A forged handshake can't complete, so VERIFY ERROR and TLS 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):

  1. Apr 25 10:57:36 hostname ovpn-server[901]: 83.97.20.30:10107 SIGUSR1[soft,connection-reset] received, client-instance restarting
  2. Apr 25 10:57:49 hostname ovpn-server[901]: 83.97.20.30:29148 SIGUSR1[soft,connection-reset] received, client-instance restarting
  3. Apr 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)
failregex (2)
^(?:\[\])?\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.