Skip to content
Back to Blog

Defeating the Docker Firewall Bypass: How to Make Fail2Ban and Nftables Actually Block Bots

Article

June 26, 2026

Ever set up Fail2Ban to block bots scanning for files like /.env or /wp-admin, only to find out the bots are still getting through with a 404 Not Found response?

If you are running a reverse proxy like Traefik inside a Docker environment such as Coolify, you have likely fallen into a major network architecture trap. Fail2Ban proudly tells you it banned the offender, the IP shows up in your firewall list, yet the malicious requests keep hitting your application container.

Here is why that happens, and the exact production blueprint to fix it permanently.


The Problem: The Secret Docker Firewall Bypass

When you publish a container port to the host, for example by binding ports 80:80 or 443:443 in your proxy configuration, Docker uses a user-land helper process called docker-proxy and inserts network rules directly into the earliest routing chains of your Linux kernel, including PREROUTING.

Standard firewall configurations drop traffic at the INPUT chain. Because Docker intercepts the packet at the PREROUTING stage, it bypasses your standard OS firewall rules completely. The traffic is handed straight to your proxy container, which processes the request and serves a 404 Not Found page. Your server is still doing the heavy lifting for an attacker who should have been blocked at the gate.

The Goal

To stop this, we need to instruct Fail2Ban to attach its defensive blocks directly into the prerouting checkpoint with an aggressive execution priority -100. This drops the malicious packets before Docker can intercept and route them into your virtual networks.


The Step-by-Step Fix (For Nftables & Traefik)

This setup is tailored for modern Linux servers utilizing Nftables as their host firewall engine alongside a containerized proxy architecture.

Step 1: Create a Fail2Ban Filter for Traefik Logs

Fail2Ban parses log text strings but strictly requires a special capture tag <HOST> somewhere in the regex pattern to figure out exactly which IP address to ban.

Create a custom filter file on your server:

sudo nano /etc/fail2ban/filter.d/traefik-bots.conf

Paste the following rules to target common malicious paths and empty or spoofed User-Agents:

[Definition]

# Safely pull the client IP straight from Traefik's access logs
failregex = ^.*"ClientAddr":"<HOST>(:\d+)?".*"RequestPath":"(/\.env|/\.git|/wp-config\.php).*".*$
            ^.*"ClientAddr":"<HOST>(:\d+)?".*"User-Agent":\[""\].*$
ignoreregex =

Step 2: Configure the High-Priority Pre-Routing Jail

Next, tell Fail2Ban how to enforce the ban. We need to override the default firewall action to hook into the network stack at the absolute point of entry.

Create your jail configuration file:

sudo nano /etc/fail2ban/jail.d/traefik.local

Paste the configuration below. Important Pro-Tip: Do not append inline comments like # directly next to numeric settings on the same line, as Fail2Ban's configuration parser will read the text as part of the number and crash.

[traefik-bots]
enabled = true
port = http,https
filter = traefik-bots
logpath = /data/coolify/proxy/access.log
backend = auto

# THE ULTIMATE FIX: Intercept during PREROUTING before Docker takes over
banaction = nftables[chain_hook=prerouting, chain_priority=-100]
protocol = tcp

# Penalty Settings
findtime = 600
maxretry = 2
bantime = 86400

# Whitelist Protection: Put your home or office IP block here so you don't ban yourself
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24

Note: The ignoreip parameter supports broad network blocks. If your ISP rotates your address within a local subnet, whitelisting that subnet can help avoid accidental lockouts while testing.


Step 3: Clear Stale Sockets and Cold-Boot

Fail2Ban will crash on startup if the target log file specified in logpath does not exist on disk yet. Make sure the directory structure is established before starting the service:

# Ensure the Traefik log file is present on the host
sudo mkdir -p /data/coolify/proxy/
sudo touch /data/coolify/proxy/access.log
sudo chmod 644 /data/coolify/proxy/access.log

# Force a clean daemon restart and clear legacy locks
sudo systemctl stop fail2ban
sudo rm -f /var/run/fail2ban/fail2ban.sock
sudo systemctl start fail2ban

Verification: Testing the Armor

Now you want to verify that your configuration is running smoothly and dropping traffic exactly as intended.

1. Check if the Jail is Active

Run the client status check to confirm Fail2Ban successfully initialized the tracking threads:

sudo fail2ban-client status traefik-bots

You should see a clean output listing your log path and 0 currently banned IPs.

2. Run a Real-World Safe Test

To test it safely without killing your own access, disconnect a secondary device such as your mobile phone from your local Wi-Fi and switch it to mobile data. This gives it an entirely separate public IP address outside your whitelist.

Open a live terminal log watcher on your server:

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

Now use your phone to navigate to your domain and intentionally type a forbidden path twice, for example https://yourdomain.com/.env.

Watch your server terminal. On the first attempt, you will see a Found notice. On the second attempt, Fail2Ban will issue an immediate Ban action. Instantly, your phone's browser will spin endlessly and hit a hard connection timeout. The 404 pages are gone because the attacker's packets are being dropped completely at the kernel layer.

3. Emergency Release (If You Lock Yourself Out)

If you ever accidentally ban yourself while testing, do not panic. Log in to your VPS cloud provider's web dashboard and launch their built-in VNC or Web Console. Because VNC operates on a separate out-of-band port, your firewall will not block it.

Once inside the console terminal, run this command to instantly release all active bans across the server:

sudo fail2ban-client unban --all
Cloud Infrastructure Security
Share this article on: X Facebook LinkedIn