Fail2ban monitors log files for repeated authentication failures and blocks malicious IP addresses at the firewall layer. Running Fail2ban in a Docker container allows it to protect both host-level SSH access and containerized web applications like Nginx Proxy Manager without installing additional packages directly on the host system.
Prerequisites
The following items are required:
- Ubuntu or Debian host with Docker Engine and Docker Compose installed
- Nginx Proxy Manager container already deployed
- Root or sudo access to create directories and manage iptables rules
- Basic familiarity with iptables firewall concepts
How Containerized Fail2ban Accesses Host Resources
A Fail2ban container requires access to two host-level resources to enforce bans across the entire server:
- Host log files mounted read-only into the container so Fail2ban filters can parse authentication attempts in real time
- Host iptables firewall accessed through network_mode: host and Linux capabilities NET_ADMIN and NET_RAW, allowing the container to insert DROP or REJECT rules directly into active iptables chains
Create Directory Structure
Create a dedicated directory structure for Fail2ban configuration files, custom filters, and persistent state data:
mkdir -p /opt/containers/fail2ban/config/jail.d
mkdir -p /opt/containers/fail2ban/config/filter.d
mkdir -p /opt/containers/fail2ban/dataConfigure Main Jail Settings
Create /opt/containers/fail2ban/config/jail.local with global defaults and jail definitions:
[DEFAULT]
# Ignore local subnets and internal Docker bridge networks
ignoreip = 127.0.0.1/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
# Ban duration: 1 hour, evaluation window: 10 mins, max retries: 5
bantime = 1h
findtime = 10m
maxretry = 5
# Firewall backend action using host iptables
banaction = iptables-multiport
banaction_allports = iptables-allports
# Host SSH Protection
[sshd]
enabled = true
port = ssh
logpath = /var/log/host_logs/auth.log
backend = polling
# Nginx Proxy Manager Bad Requests and Auth Failures
[npm-auth]
enabled = true
port = http,https
filter = npm-auth
logpath = /var/log/npm_logs/*access.log
maxretry = 3
findtime = 5m
bantime = 2h
backend = polling
The sshd jail monitors the host auth.log file for SSH authentication failures. The npm-auth jail watches Nginx Proxy Manager access logs for HTTP 401, 403, and 400 status codes that indicate unauthorized access attempts or malicious probes.
Create Custom Nginx Proxy Manager Filter
Create /opt/containers/fail2ban/config/filter.d/npm-auth.conf to match HTTP error codes in Nginx Proxy Manager access logs:
[Definition]
# Match 401/403 responses and unauthorized API access attempts in NPM access logs
failregex = ^<HOST> - .* "(GET|POST|HEAD|PUT|DELETE) .* HTTP/.*" (401|403) .*$
^<HOST> - .* ".*" 400 .*$
ignoreregex =The failregex pattern captures the client IP address and matches lines containing HTTP status codes 401 (Unauthorized), 403 (Forbidden), or 400 (Bad Request).
Create Docker Compose Configuration
Create /opt/containers/fail2ban/docker-compose.yml:
version: "3.8"
services:
fail2ban:
image: crazymax/fail2ban:latest
container_name: fail2ban
network_mode: host
restart: unless-stopped
cap_add:
- NET_ADMIN
- NET_RAW
volumes:
- /opt/containers/fail2ban/config:/data/config
- /opt/containers/fail2ban/data:/data/db
# Mount Host SSH logs (Read-Only)
- /var/log/auth.log:/var/log/host_logs/auth.log:ro
# Mount Nginx Proxy Manager logs (Read-Only)
- /opt/containers/npm/data/logs:/var/log/npm_logs:ro
# Host time sync
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
environment:
- TZ=Europe/BerlinAdjust the TZ environment variable to match your server timezone. If your Nginx Proxy Manager container stores logs in a different location, update the npm logs volume mount path accordingly.
Deploy Container Stack
Start the Fail2ban container:
docker compose -f /opt/containers/fail2ban/docker-compose.yml up -dVerify the container is running:
docker ps | grep fail2banCheck container logs for startup errors:
docker logs fail2banVerify Active Jails
Check which jails are currently enabled and running:
docker exec -it fail2ban fail2ban-client statusExpected output:
Status
|- Number of jail: 2
`- Jail list: npm-auth, sshdTest Filter Patterns
Verify that your custom filter regex matches actual log entries without triggering real bans. Run fail2ban-regex against a live Nginx Proxy Manager access log:
docker exec -it fail2ban fail2ban-regex /var/log/npm_logs/default-host_access.log /data/config/filter.d/npm-auth.confThe output displays matched lines and statistics showing how many log entries would trigger a ban under current filter rules.
Check Banned IP Addresses
Inspect the current ban list for a specific jail:
docker exec -it fail2ban fail2ban-client status npm-authThe output shows total failures detected, currently banned IP addresses, and ban statistics.
Unban an IP Address
If you accidentally trigger a ban during testing or need to remove a legitimate client from the ban list, use the unbanip command:
docker exec -it fail2ban fail2ban-client set npm-auth unbanip YOUR_IP_ADDRESSReplace YOUR_IP_ADDRESS with the actual IP address to unban. To unban an IP from all active jails, specify the sshd jail name or use the global unban command.
Adding Protection for Additional Services
Protecting other containerized applications follows a three-step pattern:
Step 1: Expose Application Logs
Ensure the application writes access or authentication logs to a persistent host directory. Add a volume mount in the Fail2ban docker-compose.yml file to make those logs available inside the Fail2ban container at a path like /var/log/service_logs.
Step 2: Create a Custom Filter
Write a new filter definition in /opt/containers/fail2ban/config/filter.d/service-name.conf. The filter must include a failregex pattern that matches failed authentication attempts in the application log format. Use fail2ban-regex to test the pattern against actual log files before deployment.
Step 3: Enable the Jail
Add a new jail block in /opt/containers/fail2ban/config/jail.local specifying the service name, ports to block, logpath pointing to the mounted log directory, and the name of the filter created in step 2. Set maxretry, findtime, and bantime values appropriate for the service threat model.
After making configuration changes, restart the Fail2ban container:
docker restart fail2banVerify the new jail appears in the active jail list using fail2ban-client status.
Monitoring Fail2ban Activity
Monitor real-time Fail2ban activity by tailing container logs:
docker logs -f fail2banFail2ban logs show when IPs are banned, which jail triggered the ban, and when bans expire. This output is useful for identifying attack patterns and verifying that filters are working correctly.
Adjusting Ban Parameters
The default configuration sets conservative ban thresholds. Adjust these values in jail.local based on observed attack patterns:
- maxretry controls how many failures trigger a ban (lower values are more aggressive)
- findtime sets the window in which failures are counted (shorter windows catch rapid attacks)
- bantime determines how long an IP remains blocked (longer bans reduce repeat offenders)
Changes to jail.local require a container restart to take effect.
Troubleshooting Common Issues
Fail2ban Cannot Modify iptables
If logs show permission errors when attempting to add firewall rules, verify that the container includes network_mode: host and both NET_ADMIN and NET_RAW capabilities in the docker-compose.yml file. Rootless Docker installations may require additional configuration to access host iptables.
Jail Shows Zero Failures Despite Visible Attacks
Verify that log file paths inside the container match the paths specified in jail.local. Check that log files are actively being written by the source application and that volume mounts are not stale. Use docker exec to inspect the mounted log directory inside the Fail2ban container.
Filter Regex Does Not Match Log Format
Application log formats change between versions. If a filter stops working after an application update, extract a sample of recent failed authentication log lines and test them against your filter using fail2ban-regex. Adjust the failregex pattern to match the new format.
Persistent Ban Database
The /opt/containers/fail2ban/data directory contains the Fail2ban SQLite database that tracks ban history and current ban states. This database persists across container restarts. To reset all bans and statistics, stop the container, delete the database file, and restart the container.
AI assistance is used on this site for language, formatting, and turning research into a consistent template. It is not used to perform the underlying research or verify technical claims. Every command, configuration, and step in this post is tested by hand before publication.