How to Install Fail2Ban on Arch Linux

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 sudo for 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 like 1h, 1d, 2w, or 1mo.
  • findtime — Time window for counting failures. If an IP triggers maxretry failures within this window, it gets banned.
  • maxretry — Number of failures allowed within findtime before 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. Use systemd when a service logs to the systemd journal; use auto (or another file backend) when you point a jail at a file logpath.
  • banaction — Firewall command to execute bans. The default on Arch Linux is iptables-multiport. Alternatives include nftables, ufw, and Firewalld actions like firewallcmd-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 ignoreip to 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:

  • None or 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.
  • NOFAIL markers in the filter list — These patterns are useful context for multi-line parsing, but they are not counted as failures toward maxretry. 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 -v and confirm the matching lines actually extract a host/IP. Matches that do not extract a host cannot be banned, and NOFAIL-tagged patterns are not counted as failures toward maxretry.

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.log does not exist or is empty, check the logtarget setting in /etc/fail2ban/fail2ban.conf and 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 IP events, including some from legitimate clients with network issues. Use a higher maxretry value (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.local file already contains a [DEFAULT] section, add only the banaction lines 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=strict makes the filesystem read-only except for explicitly allowed paths
  • ReadWritePaths grants write access only to directories Fail2ban needs (its database, log, and socket files)
  • PrivateDevices and PrivateTmp isolate the service from system devices and temporary files
  • CapabilityBoundingSet restricts 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:

  • None or missing host/IP extraction on a match line, indicating the pattern matched but nothing can be safely banned
  • NOFAIL markers in the filter list, which indicate patterns that match but are not counted as failures toward maxretry
  • 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

Does Fail2ban work with SSH key authentication only?

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.

How do I permanently whitelist my IP?

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.

Why does aggressive mode not ban all connection attempts?

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.

Can I use Fail2ban with Firewalld instead of iptables?

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.

Do bans persist after Fail2ban restarts or system reboots?

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).

How do I configure progressively longer ban times for repeat offenders?

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.

How can I test if Fail2ban is working before real attacks happen?

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.

Should I increase the SSH log level for better Fail2ban detection?

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.

Leave a Comment

Let us know you are human: