How to Install Fail2Ban on Arch Linux

Install Fail2ban on Arch Linux using pacman. Configure SSH jails, test filters with fail2ban-regex, harden it, and remove cleanly.

Last updatedAuthorJoshua JamesRead time12 minGuide typeArch Linux

Arch servers exposed to SSH, Nginx, or other login surfaces quickly collect repeated authentication failures. Install Fail2ban on Arch Linux when you want those patterns converted into temporary firewall bans instead of leaving every retry to the service alone.

The Arch package comes from the official Extra repository and provides the fail2ban-client and fail2ban-regex tools used here. After installation, the important work is choosing the right log backend, matching the firewall action to your system, and testing filters before trusting them on an exposed service. For Arch-specific background, see the Arch Wiki Fail2ban documentation.

Install Fail2ban on Arch Linux via Pacman

Fail2ban does not ship by default on Arch Linux. Install the official fail2ban package from Arch Extra after refreshing the package databases:

sudo pacman -Syu

These commands use sudo for tasks 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.

Install Fail2ban:

sudo pacman -S fail2ban

Verify the installed command:

fail2ban-client --version

Example output (your version may be newer):

Fail2Ban v1.1.0

Enable and Start Fail2ban on Arch Linux

Fail2ban runs as a systemd service. Enable it at boot and start it immediately:

sudo systemctl enable --now fail2ban

Confirm the service is running without opening the status pager:

systemctl is-active fail2ban

A running service returns:

active

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

Use local override files instead of editing package-owned defaults. Global defaults such as banaction, bantime.increment, and shared ignoreip entries can live in /etc/fail2ban/jail.local under a single [DEFAULT] section or in a clearly named defaults file under /etc/fail2ban/jail.d/. Per-service jails are cleaner as separate files under /etc/fail2ban/jail.d/, such as /etc/fail2ban/jail.d/sshd.local.

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. The package also ships action files for nftables, ufw, and Firewalld actions such as firewallcmd-rich-rules.
  • allowipv6 – IPv6 handling for Fail2ban itself. The packaged default is auto, so most Arch systems do not need to set it manually unless you intentionally force IPv6 support on or off.

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 Fail2ban SSH Jail on Arch Linux

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.

If your SSH server accepts only public keys and password authentication is disabled, Fail2ban is no longer the main control preventing SSH password compromise. It can still reduce repeated pre-authentication noise, slow probing traffic, and protect other services that still expose login prompts.

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 stable admin IP address or subnet to ignoreip before testing aggressive ban settings. Keep the whitelist narrow; broad office, VPN, or hosting-provider ranges can hide real abuse from those networks.

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 Fail2ban 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 on Arch Linux

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

If journalctl reports Failed to parse timestamp, retype the quotes as straight ASCII quotes or use single quotes in your shell. Curly quotes copied from rich text become part of the timestamp string and journalctl cannot parse them.

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 use the Linux tail command to check recent events directly:

sudo tail -50 /var/log/fail2ban.log

Test Fail2ban 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 Fail2ban Jails on Arch Linux

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 the Fail2ban 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. If /etc/fail2ban/fail2ban.local does not already exist, create it with:

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.

Validate the configuration and restart Fail2ban to apply the database and jail changes:

sudo fail2ban-client -t
sudo systemctl restart fail2ban

Use bantime.increment for Progressive Bans

If you prefer progressive bans without a separate recidive jail, enable bantime.increment in a shared [DEFAULT] override. Fail2ban uses its SQLite database to track previous bans and increase the next ban time for repeat offenders.

sudo tee /etc/fail2ban/jail.d/00-ban-policy.local > /dev/null <<'EOF'
[DEFAULT]
bantime.increment = true
bantime.factor = 1
bantime.maxtime = 1d
EOF

Use either recidive, bantime.increment, or a deliberately combined policy. If you already keep shared defaults in /etc/fail2ban/jail.local, place these settings under the existing [DEFAULT] header instead of creating a duplicate policy file.

sudo fail2ban-client -t
sudo systemctl restart fail2ban

Handle Pre-Authentication SSH Attacks with Fail2ban

Why These Attacks Slip Past Default Filters

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 [preauth]

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:

Create a Custom Pre-Auth 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+(?: \[preauth\])?\s*$
ignoreregex =
EOF

Create a separate jail for pre-authentication attacks:

Create a Dedicated Jail

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:

Reload and Verify

sudo fail2ban-client reload
sudo fail2ban-client status sshd-preauth

Configure the Fail2ban Firewall Backend on Arch Linux

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 one default banaction file to match the firewall you actually use:

Choose one firewall backend and configure it once. If you already manage shared defaults in /etc/fail2ban/jail.local, add the selected banaction lines under that existing [DEFAULT] section instead of creating another defaults file.

Use nftables

nftables is the modern replacement for iptables and is common on newer systems:

sudo tee /etc/fail2ban/jail.d/00-firewall.local > /dev/null <<'EOF'
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
EOF

Use UFW

sudo tee /etc/fail2ban/jail.d/00-firewall.local > /dev/null <<'EOF'
[DEFAULT]
banaction = ufw
EOF

Use Firewalld

sudo tee /etc/fail2ban/jail.d/00-firewall.local > /dev/null <<'EOF'
[DEFAULT]
banaction = firewallcmd-rich-rules
EOF

Validate the configuration and restart Fail2ban after changing the banaction:

sudo fail2ban-client -t
sudo systemctl restart fail2ban

Harden Fail2ban on Arch Linux with a systemd Drop-In

Create a systemd Drop-In Override

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=-/run/fail2ban
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, restart Fail2ban, and confirm the client can still reach the service socket:

sudo systemctl daemon-reload
sudo systemctl restart fail2ban
systemctl is-active fail2ban
sudo fail2ban-client status

What These Hardening Options Do

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)

Troubleshoot Fail2ban on Arch Linux

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 the matching Fail2ban chain before deleting anything manually:

sudo iptables -L f2b-sshd -n --line-numbers | grep 192.0.2.1

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:

sudo journalctl -u fail2ban --since "10 minutes ago" --no-pager | grep -i "error\|warning"

Confirm the command for your selected backend exists. Only the backend you chose needs to be present; Firewalld and UFW also need their own services configured as described in their linked Arch guides.

command -v iptables
command -v nft

If you choose UFW or Firewalld instead, verify that frontend after installing and enabling it with command -v ufw or command -v firewall-cmd. The default iptables-multiport action works on a standard Arch installation after the official package dependencies are installed.

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.

To test the ban path without waiting for real attacks, manually ban a documentation IP such as 192.0.2.1, check the jail and firewall output, then unban it immediately with the commands in the ban-management section.

Remove Fail2ban from Arch Linux

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

Verify the package is no longer installed:

pacman -Q fail2ban

A removed package returns:

error: package 'fail2ban' was not found

Optionally remove local configuration, ban state, article-created hardening drop-ins, and Fail2ban logs:

This permanently deletes your jail configurations, ban database, local systemd override, and Fail2ban log history. Make a backup first if you might need them again.

sudo rm -rf /etc/fail2ban /var/lib/fail2ban /etc/systemd/system/fail2ban.service.d
sudo rm -f /var/log/fail2ban.log /var/log/fail2ban.log.*
sudo systemctl daemon-reload

Conclusion

Fail2ban is most useful on Arch Linux when the package install, jail backend, firewall action, and filter test all agree with the same service logs. Keep SSH protection simple with the journal-backed sshd jail, use fail2ban-client for temporary ban management, and validate custom filters with fail2ban-regex -v before relying on them for exposed services.

Share this guide

Help another Linux user troubleshoot faster

Share this guide with someone troubleshooting Linux systems or saving it for later.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show our tutorials more often in Top Stories and mark them as preferred in AI Mode and AI Overviews when relevant.

Add LinuxCapable as a preferred source on Google
Search LinuxCapable

Need another guide?

Search LinuxCapable for package installs, commands, troubleshooting, and follow-up guides related to what you just read.

Found this guide useful?

Support LinuxCapable to keep tutorials free and up to date.

Buy me a coffeeBuy me a coffee
Before commenting, please review our Comments Policy.
Formatting tips for your comment

You can use basic HTML to format your comment. Useful tags currently allowed in published comments:

You type Result
<code>command</code> command
<strong>bold</strong> bold
<em>italic</em> italic
<a href="https://example.com">link</a> link
<blockquote>quote</blockquote> quote block

Add to the discussion

Questions, fixes, command output, and version notes help keep this guide current.

Verify before posting: