This guide walks you through how to install Fail2ban on Arch Linux and configure it to slow down brute-force attacks against services like SSH and HTTP authentication. Fail2ban watches your logs for repeated failures, then applies temporary firewall bans for the source IPs that match those patterns.
You will set up a practical SSH jail, learn how to test filters against real log data (so you can trust what you deploy), and add a few optional hardening and escalation techniques for noisy environments. If your SSH server already uses public key authentication only, Fail2ban is less critical for preventing compromise, but it can still reduce log noise and help with other services. For additional reference, see the Arch Wiki Fail2ban documentation.
Install Fail2ban via Pacman
Fail2ban does not ship by default on Arch Linux but is available in the official repositories. Before installing, update your package database to ensure you get the latest version:
This guide uses
sudofor commands that need root privileges. If your user is not in the sudoers file yet, run the commands as root or follow the guide on how to add and manage sudo users on Arch Linux.
sudo pacman -Syu
Install Fail2ban:
sudo pacman -S fail2ban
Verify the installation by checking the version:
fail2ban-client --version
Example output (your version may be newer):
Fail2Ban v1.1.0
Enable and Start the Fail2ban Service
Fail2ban runs as a systemd service. Enable it to start automatically at boot and start it immediately:
sudo systemctl enable --now fail2ban
Confirm the service is running:
sudo systemctl status fail2ban
Example output (your timestamps and PID will differ):
● fail2ban.service - Fail2Ban Service
Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled; preset: disabled)
Active: active (running)
Understand Fail2ban Configuration Files on Arch Linux
Know Which Files to Edit
Fail2ban uses a hierarchy of configuration files that allows you to customize behavior without losing settings during package updates. The main configuration file /etc/fail2ban/jail.conf contains default settings, but you should never edit this file directly. When Pacman updates Fail2ban, your changes would be overwritten or create .pacnew files requiring manual merging.
Use Local Overrides for Your Changes
Instead, create a local override file:
sudo touch /etc/fail2ban/jail.local
Settings in jail.local override the defaults from jail.conf. For per-jail configuration, create individual files in /etc/fail2ban/jail.d/ such as /etc/fail2ban/jail.d/sshd.local. This approach keeps configurations modular and easier to manage across multiple services.
Key Settings and What They Control
Key configuration options to understand:
bantime— Duration an IP stays banned (default: 10 minutes). Use suffixes like1h,1d,2w, or1mo.findtime— Time window for counting failures. If an IP triggersmaxretryfailures within this window, it gets banned.maxretry— Number of failures allowed withinfindtimebefore banning.ignoreip— IP addresses, CIDR ranges, or hostnames to never ban. Always include your own IP to avoid locking yourself out.backend— Log monitoring method. Usesystemdwhen a service logs to the systemd journal; useauto(or another file backend) when you point a jail at a filelogpath.banaction— Firewall command to execute bans. The default on Arch Linux isiptables-multiport. Alternatives includenftables,ufw, and Firewalld actions likefirewallcmd-rich-rules.
Bans, Persistence, and the SQLite Database
Fail2ban stores active bans in a SQLite database at /var/lib/fail2ban/fail2ban.sqlite3. This means bans persist across service restarts and system reboots. The dbpurgeage setting in /etc/fail2ban/fail2ban.conf controls how long ban records are retained (default: 1 day). For tracking repeat offenders with the recidive jail, you may want to increase this value.
Configure the SSH Jail
The SSH jail is the most common use case for Fail2ban. If you have OpenSSH installed on Arch Linux, protecting it against brute-force attacks prevents log spam and reduces the risk of successful compromise if you use password authentication.
Create a dedicated jail configuration file:
sudo tee /etc/fail2ban/jail.d/sshd.local > /dev/null <<'EOF'
[sshd]
enabled = true
filter = sshd
port = ssh
backend = systemd
maxretry = 5
findtime = 1d
bantime = 2w
ignoreip = 127.0.0.1/8 ::1
EOF
These settings are deliberately strict: five failed attempts within a day results in a two-week ban. The logic is that legitimate users rarely fail authentication five times in 24 hours, while attackers running password lists will hit this threshold quickly. Adjust maxretry and bantime based on your environment and risk tolerance.
The backend = systemd setting tells Fail2ban to read SSH events from the systemd journal rather than traditional log files. This is the correct setting for Arch Linux since sshd logs to the journal by default.
Add your own IP address or subnet to
ignoreipto prevent accidentally locking yourself out. If an attacker knows your IP, they could spoof packets to trigger a ban against you, so whitelisting trusted addresses provides protection in both directions.
For better detection coverage, consider setting LogLevel VERBOSE in /etc/ssh/sshd_config. Some sshd filter patterns rely on verbose logging to capture IP addresses from connection attempts that fail before the authentication phase.
Apply the new configuration by reloading Fail2ban:
sudo systemctl reload fail2ban
Verify the SSH jail is active:
sudo fail2ban-client status sshd
Example output:
Status for the jail: sshd |- Filter | |- Currently failed: 0 | |- Total failed: 0 | `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd `- Actions |- Currently banned: 0 |- Total banned: 0 `- Banned IP list:
The Journal matches line confirms Fail2ban is monitoring the systemd journal for SSH events.
Manage Bans with fail2ban-client
The fail2ban-client command manages jails and bans at runtime. No service restart is needed for these changes.
Runtime changes made with fail2ban-client are great for quick response, but they are not a substitute for updating your jail files under /etc/fail2ban/jail.d/ when you want the behavior to persist after a restart.
View all enabled jails:
sudo fail2ban-client status
Example output:
Status |- Number of jail: 1 `- Jail list: sshd
View detailed status for a specific jail including banned IPs:
sudo fail2ban-client status sshd
View all currently banned IPs across all jails:
sudo fail2ban-client banned
Manually ban an IP address:
sudo fail2ban-client set sshd banip 192.0.2.1
Unban an IP address:
sudo fail2ban-client set sshd unbanip 192.0.2.1
Unban an IP across all jails (useful when you are not sure which jail applied the ban):
sudo fail2ban-client unban 192.0.2.1
Unban everything (all jails and the database):
sudo fail2ban-client unban --all
Reload Fail2ban configuration without restarting the service:
sudo fail2ban-client reload
View Fail2ban Logs
Confirm Where Fail2ban Writes Logs
Fail2ban writes its own logs to the target configured in /etc/fail2ban/fail2ban.conf. On Arch Linux, the default logtarget is /var/log/fail2ban.log. You can also use journalctl to see systemd unit messages (starts, stops, and errors), and you can switch logtarget to SYSTEMD-JOURNAL if you prefer journal-only logging.
To confirm the current log and database defaults quickly:
grep -E '^(logtarget|dbfile|dbpurgeage)[[:space:]]*=' /etc/fail2ban/fail2ban.conf
Check Unit Messages in the Systemd Journal
sudo journalctl -u fail2ban --since "1 hour ago" --no-pager
To follow the unit logs in real time (useful for startup failures and restarts):
sudo journalctl -u fail2ban -f
If the log file exists (it is created when Fail2ban starts writing to it), you can check recent events directly:
sudo tail -50 /var/log/fail2ban.log
Test Filters with fail2ban-regex
Before deploying custom filters or troubleshooting why bans are not working, test filters against actual log data using fail2ban-regex. This tool shows exactly what patterns match and whether they can trigger bans.
On Arch Linux, fail2ban-regex can query the systemd journal directly, which is often the cleanest way to test the SSH filter used by the sshd jail:
sudo fail2ban-regex -v systemd-journal sshd
If you want to limit testing to a specific time range (for example, the last day), export those log lines to a file first and test against that file:
sudo journalctl -u sshd --since "1 day ago" --no-pager > /tmp/sshd-journal.log
sudo fail2ban-regex -v /tmp/sshd-journal.log /etc/fail2ban/filter.d/sshd.conf
The verbose -v flag is essential for troubleshooting. Without it, you only see summary counts. With it, you see each matched line, the captured IP address, and which specific regex pattern matched.
Understanding the Output
In the verbose output, look for two critical indicators:
Noneor a missing host/IP on a match line — This usually means the pattern matched, but Fail2ban could not determine what to ban. In practice, those matches do not result in a ban.NOFAILmarkers in the filter list — These patterns are useful context for multi-line parsing, but they are not counted as failures towardmaxretry. They can make it look like a filter is matching a lot, while bans still do not happen.
If your output shows matches but no extracted IP address on the matching line, Fail2ban has nothing it can safely ban. In that case, adjust the jail to use a pattern that captures <HOST> on a failure-counting line, or create a small custom filter for your exact log format.
Testing Filter Modes
The sshd filter has multiple modes: normal (default), ddos, extra, and aggressive. Each mode adds more patterns but may include NOFAIL entries that appear to match but do not trigger bans.
sudo fail2ban-regex -v systemd-journal "sshd[mode=aggressive]"
If your SSH jail shows activity but no bans, run
fail2ban-regex -vand confirm the matching lines actually extract a host/IP. Matches that do not extract a host cannot be banned, andNOFAIL-tagged patterns are not counted as failures towardmaxretry.
If you are testing a service that logs to a file (for example, Nginx writing to /var/log/nginx/error.log), point fail2ban-regex at that file and the corresponding filter.
Configure Additional Jails
Fail2ban includes filters for many common services. Check available filters:
ls /etc/fail2ban/filter.d/
To enable additional jails, create configuration files in /etc/fail2ban/jail.d/. Here is an example for Nginx authentication:
sudo tee /etc/fail2ban/jail.d/nginx-http-auth.local > /dev/null <<'EOF'
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 1h
EOF
This jail only works if Nginx is actually writing authentication failures to the file you configured in logpath. If your Nginx error log lives elsewhere, adjust logpath to match.
sudo ls -la /var/log/nginx/error.log
sudo tail -n 20 /var/log/nginx/error.log
Reload Fail2ban to apply the new jail:
sudo fail2ban-client reload
Verify the new jail is active:
sudo fail2ban-client status
Ban Repeat Offenders with a Recidive Jail
Attackers often return after their initial ban expires. The recidive jail monitors the Fail2ban log itself and issues longer bans to IPs that get banned repeatedly across any jail. This creates an escalating response: a first offense might result in a 2-week SSH ban, but if the same IP triggers multiple bans across your jails, recidive issues a longer ban.
Create the recidive jail configuration:
sudo tee /etc/fail2ban/jail.d/recidive.local > /dev/null <<'EOF'
[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime = 1w
findtime = 1d
maxretry = 3
EOF
This configuration bans an IP for one week if it gets banned three times within a day across any jail. The banaction_allports action blocks the IP on all ports, not just the specific service that triggered the original ban.
The recidive jail requires Fail2ban to log to a file, not just the systemd journal. If
/var/log/fail2ban.logdoes not exist or is empty, check thelogtargetsetting in/etc/fail2ban/fail2ban.confand ensure it is set to/var/log/fail2ban.log.
For recidive to work effectively with long findtime windows, increase the database purge age:
sudo tee /etc/fail2ban/fail2ban.local > /dev/null <<'EOF'
[DEFAULT]
dbpurgeage = 7d
EOF
If you already have a /etc/fail2ban/fail2ban.local file, add dbpurgeage = 7d under the [DEFAULT] section instead of overwriting it.
Reload Fail2ban to apply the changes:
sudo fail2ban-client reload
Handle Pre-Authentication SSH Attacks
Some attackers probe SSH servers without completing authentication. These pre-authentication connection attempts (key exchange failures, protocol mismatches) generate log entries like:
error: kex_exchange_identification: Connection closed by remote host Connection closed by 192.168.1.50 port 54321
The default sshd filter and even aggressive mode may not catch these consistently. The kex_exchange_identification line matches but contains no IP address. The Connection closed by IP line has the IP but may be marked with a NOFAIL flag that prevents banning.
To catch these attacks reliably, create a custom filter:
sudo tee /etc/fail2ban/filter.d/sshd-preauth.local > /dev/null <<'EOF'
[INCLUDES]
before = common.conf
[Definition]
_daemon = sshd
failregex = ^%(__prefix_line)sConnection closed by <HOST> port \d+\s*$
ignoreregex =
EOF
Create a separate jail for pre-authentication attacks:
sudo tee /etc/fail2ban/jail.d/sshd-preauth.local > /dev/null <<'EOF'
[sshd-preauth]
enabled = true
filter = sshd-preauth
port = ssh
backend = systemd
maxretry = 10
findtime = 10m
bantime = 1h
ignoreip = 127.0.0.1/8 ::1
EOF
This filter catches all
Connection closed by IPevents, including some from legitimate clients with network issues. Use a highermaxretryvalue (10 or more) to avoid false positives.
Reload Fail2ban and verify the new jail is active:
sudo fail2ban-client reload
sudo fail2ban-client status sshd-preauth
Configure Firewall Backend
Fail2ban defaults to iptables-multiport for ban actions. This works out of the box on most systems, but if you use a different firewall frontend, configure the banaction to match. On Arch Linux, common alternatives include UFW and Firewalld.
Set the default banaction in /etc/fail2ban/jail.local to match your firewall:
Choose one firewall backend and configure it once. If your
/etc/fail2ban/jail.localfile already contains a[DEFAULT]section, add only thebanactionlines under that existing section instead of adding a second[DEFAULT]header.
Use nftables
nftables is the modern replacement for iptables and is common on newer systems:
sudo tee -a /etc/fail2ban/jail.local > /dev/null <<'EOF'
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
EOF
Use UFW
sudo tee -a /etc/fail2ban/jail.local > /dev/null <<'EOF'
[DEFAULT]
banaction = ufw
EOF
Use Firewalld
sudo tee -a /etc/fail2ban/jail.local > /dev/null <<'EOF'
[DEFAULT]
banaction = firewallcmd-rich-rules
EOF
Restart Fail2ban after changing the banaction:
sudo systemctl restart fail2ban
Harden the Fail2ban Service
Fail2ban requires root privileges, but you can limit its capabilities using a systemd drop-in file. This restricts filesystem access and limits what the service can do beyond its core functionality.
Create the drop-in directory and configuration:
sudo mkdir -p /etc/systemd/system/fail2ban.service.d
sudo tee /etc/systemd/system/fail2ban.service.d/override.conf > /dev/null <<'EOF'
[Service]
PrivateDevices=yes
PrivateTmp=yes
ProtectHome=read-only
ProtectSystem=strict
ReadWritePaths=-/var/run/fail2ban
ReadWritePaths=-/var/lib/fail2ban
ReadWritePaths=-/var/log/fail2ban.log
ReadWritePaths=-/run/xtables.lock
CapabilityBoundingSet=CAP_AUDIT_READ CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_NET_RAW
EOF
Reload systemd and restart Fail2ban:
sudo systemctl daemon-reload
sudo systemctl restart fail2ban
These settings limit what Fail2ban can access:
ProtectSystem=strictmakes the filesystem read-only except for explicitly allowed pathsReadWritePathsgrants write access only to directories Fail2ban needs (its database, log, and socket files)PrivateDevicesandPrivateTmpisolate the service from system devices and temporary filesCapabilityBoundingSetrestricts capabilities to reading logs (CAP_AUDIT_READ), accessing log files (CAP_DAC_READ_SEARCH), and managing firewall rules (CAP_NET_ADMIN,CAP_NET_RAW)
Troubleshooting Fail2ban
Jail Shows 0 Matches Despite Failed Logins
This is the most common issue. First, verify that failed login attempts are actually reaching the systemd journal:
sudo journalctl -u sshd --since "1 hour ago" --no-pager | grep -Ei "failed|invalid"
If you see failures in the journal but the jail shows zero matches, the issue is usually with filter matching. Test the filter directly:
sudo fail2ban-regex -v systemd-journal sshd
In the verbose output, check for:
Noneor missing host/IP extraction on a match line, indicating the pattern matched but nothing can be safely bannedNOFAILmarkers in the filter list, which indicate patterns that match but are not counted as failures towardmaxretry- Zero matches entirely, suggesting the regex does not match your log format
If failure patterns match but show None for HOST, the filter cannot determine what to ban. You may need a custom filter targeting the specific log format of your failing connections.
Service Fails to Start
Check for configuration syntax errors:
sudo fail2ban-client -t
This command parses all configuration files and exits on the first error, showing the file and line with the problem. Common causes include:
- Missing quotes around values with special characters
- Duplicate section headers in the same file
- Referencing filters or actions that do not exist
- Incorrect indentation (INI format requires consistent spacing)
Check the systemd journal for startup errors:
sudo journalctl -xeu fail2ban
Cannot Unban IP Address
First verify which jail banned the IP:
sudo fail2ban-client banned
Use the correct jail name when unbanning:
sudo fail2ban-client set sshd unbanip 192.0.2.1
If the IP persists in your firewall even after unbanning, the firewall rule may have been added manually or by another tool. Check and remove it directly:
sudo iptables -L -n | grep 192.0.2.1
sudo iptables -D f2b-sshd -s 192.0.2.1 -j REJECT
Firewall Rules Not Created
Verify the banaction matches your firewall. If you are using nftables but banaction is set to iptables, rules may fail silently or be created in a firewall you are not monitoring.
Check Fail2ban logs for action errors:
journalctl -u fail2ban --since "10 minutes ago" | grep -i "error\|warning"
Verify the firewall service is running:
systemctl status iptables nftables firewalld 2>/dev/null
If using iptables, verify the fail2ban chains exist:
sudo iptables -L -n | grep -E "^Chain f2b"
Verify a Ban Is Actually Blocking Traffic
To confirm bans are working, check the firewall for the banned IP. For iptables:
sudo iptables -L f2b-sshd -n -v
For nftables:
sudo nft list set inet f2b-table addr-set-sshd
You should see the banned IP in the output. If the IP is not present, check that the banaction configuration matches your firewall backend.
Remove Fail2ban
If you no longer need Fail2ban, stop and disable the service first:
sudo systemctl disable --now fail2ban
Remove the package and its dependencies:
sudo pacman -Rns fail2ban
Optionally remove configuration files and data:
This permanently deletes all your jail configurations and ban database. Make a backup first if you might need them again.
sudo rm -rf /etc/fail2ban /var/lib/fail2ban
Frequently Asked Questions
When SSH is configured to only allow key authentication and password authentication is disabled, Fail2ban provides minimal benefit for SSH protection. Attackers cannot brute-force a service that does not accept passwords. However, Fail2ban can still reduce log noise from repeated connection attempts and protect other services on the same server.
Add your IP address or subnet to the ignoreip setting in /etc/fail2ban/jail.local under the [DEFAULT] section to whitelist it across all jails, or in a specific jail section to whitelist only for that service. Separate multiple addresses with spaces.
Aggressive mode adds patterns for DDoS-style attacks and protocol failures, but many of these patterns use internal NOFAIL flags. These patterns help link multi-line log entries but do not directly trigger bans. For specific attack patterns like kex_exchange_identification failures, you may need a custom filter that explicitly captures the IP address on a failure-counting line.
Yes. Set banaction = firewallcmd-rich-rules in your jail configuration to use Firewalld rich rules for banning. Fail2ban includes several Firewalld action files in /etc/fail2ban/action.d/ for different use cases.
Yes. Fail2ban stores active bans in a SQLite database at /var/lib/fail2ban/fail2ban.sqlite3. Bans persist across service restarts and system reboots. The dbpurgeage setting in /etc/fail2ban/fail2ban.conf controls how long ban history is retained (default: 1 day).
Enable bantime.increment = true in your jail.local [DEFAULT] section. This uses the SQLite database to track previous bans and increase bantime for repeat offenders. The default jail.conf documentation describes exponential growth (1, 2, 4, 8, …) based on the prior ban count; you can tune the behavior using bantime.factor, bantime.multipliers, or bantime.formula, and cap it with bantime.maxtime.
Temporarily lower maxretry to 2 in your jail configuration. From a different IP (or using a VPN), attempt to SSH with an incorrect password 2-3 times. Check if that IP appears in fail2ban-client status sshd. Remember to restore the original maxretry value and unban your test IP afterward.
Setting LogLevel VERBOSE in /etc/ssh/sshd_config improves Fail2ban detection coverage by logging more connection details. Some sshd filter patterns require VERBOSE logging to extract IP addresses from connection attempts that fail before authentication. The tradeoff is increased log volume.
Conclusion
You now have Fail2ban running on Arch Linux with an SSH jail that reads the systemd journal and bans abusive IPs through your firewall. Confirm it is working with fail2ban-client status sshd, and validate any filter changes with fail2ban-regex -v before trusting them. If you deal with noisy scans, add the pre-auth jail and recidive (or bantime.increment) to escalate repeat offenders without over-banning legitimate users.