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
sudofor 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 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. The package also ships action files fornftables,ufw, and Firewalld actions such asfirewallcmd-rich-rules.allowipv6– IPv6 handling for Fail2ban itself. The packaged default isauto, 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
ignoreipbefore 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:
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 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.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. 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 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:
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 selectedbanactionlines 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=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)
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:
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 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.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><a href="https://example.com">link</a><blockquote>quote</blockquote>