fail2ban and IPv6: Coverage Gaps and Practical Configuration
fail2ban is one of the most widely deployed brute-force protection tools on Linux servers, scanning log files for malicious patterns and dynamically banning offending IP addresses through firewall rules. Its IPv6 support, however, has a history of gaps — incomplete in early versions, added incrementally through action system changes, and still carrying configuration assumptions that silently leave IPv6 traffic unprotected on many production servers. This page documents the specific coverage gaps, the version-dependent behaviour, the configuration required for genuine dual-stack protection, and the testing procedure to verify that bans actually apply to IPv6 attackers. The findings are based on testing across fail2ban 0.9.x through 0.11.x on Debian and Ubuntu server installations with both iptables and nftables backends. This page sits within the security section and connects to the privacy and security topic hub.
The core problem
Many fail2ban installations protect IPv4 but silently ignore IPv6 because the default action configuration only invokes iptables (IPv4), not ip6tables (IPv6). An attacker targeting your SSH, web server, or mail server over IPv6 will not trigger a ban even if the same attack over IPv4 would be blocked within minutes. If your server has an IPv6 address — and most modern servers do — you may have a brute-force protection gap you have never tested for.
This is not a theoretical concern. IPv6 address space is actively used for automated attacks. Botnets have adopted IPv6. Scanning tools operate over both protocol families. An attacker who discovers that your fail2ban installation only blocks IPv4 addresses can trivially switch to IPv6 and continue their attack unimpeded.
Version history of IPv6 support
The IPv6 story in fail2ban is one of incremental improvement over a long period:
fail2ban 0.8.x and earlier: No IPv6 support at all. The log parsing could match IPv6 addresses in patterns, but the action system had no mechanism to create ip6tables rules. Matched IPv6 addresses were recognised but not banned. This was the state of fail2ban for years on Debian Wheezy, Ubuntu 14.04, and CentOS 6/7 — distributions that many servers ran well into the late 2010s.
fail2ban 0.10.x and 0.11.x: IPv6 support is implemented in the action system. The iptables-multiport action, iptables-allports action, and the nftables actions all handle IPv6 addresses when configured correctly. The filter system parses IPv6 addresses from logs. However, correct configuration is not the default in all installations — it depends on which action is configured and how the jail was set up.
fail2ban 0.10.0 (released 2017) was the first version with proper IPv6 ban support in the action system. The iptables-multiport action was updated to detect whether a matched address is IPv4 or IPv6 and invoke the appropriate firewall command. Prior versions would match IPv6 addresses in log patterns but had no way to create the corresponding firewall rule, making the match operationally useless.
Checking your version
fail2ban-client version
If you are running anything below 0.10.0, you do not have functional IPv6 banning regardless of configuration. Upgrade first, then configure.
Where the gaps hide
Even on fail2ban 0.10.x+, several configuration patterns silently break IPv6 protection:
Gap 1: Custom actions that only call iptables
If your jail configuration uses a custom action or an action override that explicitly calls iptables without a corresponding ip6tables call, IPv6 addresses will match in the filter but the ban action will fail (usually silently, with an error logged only at debug level).
The most common source of this gap is tutorials and configuration snippets copied from pre-0.10 guides. If your jail configuration includes action = iptables-multiport[name=sshd, port=ssh, protocol=tcp] and the iptables-multiport.conf action file in your installation is from an older version, it may not include the IPv6-aware logic. Check the action file directly — it should contain conditional logic or use the iptables / ip6tables wrapper.
Gap 2: Firewall backend mismatch
If your system uses nftables but fail2ban is configured with iptables-based actions, the iptables compatibility layer may or may not forward rules to nftables correctly for IPv6. The most reliable approach on nftables systems is to use fail2ban's native nftables actions:
[DEFAULT]
banaction = nftables-multiport
banaction_allports = nftables-allports
The nftables actions handle both IPv4 and IPv6 natively because nftables uses a unified rule structure rather than separate iptables/ip6tables binaries.
Gap 3: Log format not matching IPv6 addresses
Some services log IPv6 addresses in formats that fail2ban's default filters do not match. A common case is SSH logging IPv6 addresses with square brackets in some configurations or with ::ffff: prefixed IPv4-mapped addresses. If the filter regex does not account for these formats, the attack is never detected.
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
This command tests your current SSH filter against your actual log file. Check the output for matched IPv6 addresses. If your server receives SSH connections over IPv6 (check with ss -tlnp | grep 22) but fail2ban-regex only shows IPv4 matches, the filter is not catching IPv6 entries.
Gap 4: Subnet-level attacks
IPv6 introduces a problem that IPv4 fail2ban configurations rarely had to consider: subnet rotation. An attacker with a /64 allocation has 2^64 addresses to rotate through. Banning individual addresses is ineffective when the attacker can use a new address for every attempt.
In testing with simulated brute-force attacks from a /64 IPv6 range, fail2ban's default per-address banning behaviour blocked each individual address correctly but had no measurable impact on the attack rate — the attacker simply used the next address in the range. Effective protection against subnet-rotating IPv6 attacks requires banning at the subnet level (/64 or even /48) rather than individual addresses.
Configuring subnet-level banning requires custom action configuration. The approach varies by backend:
# In jail.local, use a custom action that bans /64 subnets for IPv6
# This requires a modified action file — see below
[sshd]
enabled = true
banaction = nftables-multiport
# Custom parameter to control IPv6 ban prefix
banaction_allports = nftables-allports
Some fail2ban configurations support an ipv6_prefix parameter in the action to specify the subnet mask for IPv6 bans. If your version does not support this natively, you can create a custom action file that masks the IPv6 address to /64 before inserting the firewall rule.
Configuration for genuine dual-stack protection
Here is a complete jail configuration that provides working IPv4 and IPv6 protection on a modern Debian/Ubuntu system with nftables:
[DEFAULT]
# Use nftables for native dual-stack support
banaction = nftables-multiport
banaction_allports = nftables-allports
# Ban duration and thresholds
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
[apache-auth]
enabled = true
port = http,https
logpath = %(apache_error_log)s
[postfix]
enabled = true
port = smtp,465,submission
logpath = %(postfix_log)s
The key choice is banaction = nftables-multiport. The nftables actions use nft commands that handle both address families in a single rule set. You do not need separate IPv4 and IPv6 action configurations.
If your system still uses legacy iptables (not the nftables compatibility layer), you need to ensure that the iptables-multiport action file supports IPv6. On fail2ban 0.10.x+, the stock iptables-multiport.conf action detects the address family and calls iptables or ip6tables accordingly. If you are using a custom or overridden action file, verify that it includes this logic.
Testing your IPv6 protection
Configuring fail2ban for IPv6 is only half the job. You need to verify that bans actually apply. The testing procedure:
Step 1: Verify fail2ban sees IPv6 addresses in logs
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf | grep -i ipv6
Or more directly, look for matched addresses that contain colons (IPv6 notation) in the full regex test output.
Step 2: Trigger a test ban
From another machine with IPv6 connectivity to your server, generate failed SSH login attempts exceeding your maxretry threshold:
for i in $(seq 1 6); do ssh -o ConnectTimeout=5 invaliduser@your-server-ipv6-address; done
Step 3: Verify the ban was created
fail2ban-client status sshd
The output should show the IPv6 address in the "Banned IP list". Then verify the firewall rule exists:
# For nftables
nft list ruleset | grep -i "your-ipv6-prefix"
# For legacy iptables
ip6tables -L -n | grep "your-ipv6-address"
If the address appears in fail2ban's ban list but not in the firewall rules, the action is failing — check fail2ban's log at /var/log/fail2ban.log with loglevel = DEBUG for the specific error.
Step 4: Test connectivity is actually blocked
From the banned IPv6 address, attempt to connect:
ssh -o ConnectTimeout=5 user@your-server-ipv6-address
The connection should time out or be refused. If it succeeds, the firewall rule is not effective — the most common cause is a rule ordering issue where an ACCEPT rule earlier in the chain takes precedence over fail2ban's REJECT or DROP rule.
Interaction with UFW and firewalld
If you manage your firewall through UFW or firewalld rather than raw iptables/nftables, the interaction with fail2ban requires attention.
UFW: fail2ban's ufw action creates rules through the ufw command, which handles both IPv4 and IPv6 natively. If you use banaction = ufw, dual-stack banning works without additional configuration, provided UFW itself has IPv6 enabled (IPV6=yes in /etc/default/ufw). The UFW vs firewalld technical note covers the differences between these firewall management tools in more detail.
firewalld: fail2ban's firewallcmd-rich-rules action uses firewall-cmd to create rich rules, which support both IPv4 and IPv6 addresses. The action should handle both address families, but test it — firewalld's zone model can produce surprising results when the IPv6 interface is in a different zone than the IPv4 interface.
Monitoring and ongoing verification
Dual-stack fail2ban protection is not a set-and-forget configuration. Each of these events can silently break IPv6 banning:
- fail2ban package upgrades can overwrite action files, reverting customisations
- Firewall backend changes (switching from iptables to nftables) require corresponding action changes
- Distribution upgrades may change log formats, breaking filter regex matches for IPv6
- Service configuration changes (moving SSH to a different port, changing log paths) need matching jail updates
Schedule a periodic check — monthly is reasonable — that runs the test procedure described above. Better yet, incorporate it into a monitoring script that alerts if a test IPv6 ban fails to produce a corresponding firewall rule.
After upgrading fail2ban, always check whether your jail.local overrides are still effective. The upgrade process preserves jail.local but may update the action files in /etc/fail2ban/action.d/. If you were relying on a stock action file that happened to include IPv6 support and the upgrade replaces it, test that IPv6 banning still works.
Practical recommendations
For most server operators, the shortest path to reliable dual-stack fail2ban protection is:
- Upgrade to fail2ban 0.10.x or later. Anything older cannot ban IPv6 addresses.
- Use nftables actions if your system supports nftables. They handle dual-stack natively and avoid the iptables/ip6tables split.
- Test with actual IPv6 traffic. Do not assume configuration is correct — verify with a real ban/verify cycle.
- Consider subnet-level banning for SSH. Individual address bans are insufficient against attackers who rotate through a /64 allocation.
- Monitor fail2ban logs for action failures. A failed ban action is often logged only at debug level and is easy to miss.
The EA Origin chat investigation covers a related theme from a different angle — security tooling that appears to provide protection but has gaps that only surface under specific conditions. Both cases illustrate the importance of verifying security mechanisms rather than assuming they work as expected.