Fixing the Fail2Ban Nightmare: A Step-by-Step Guide to Securing Your Server with Nginx Proxy Manager and Cloudflare
If you’re like me, you’ve probably spent hours trying to get Fail2Ban to work with Nginx Proxy Manager and Cloudflare. It’s a frustrating experience, to say the least. But don’t worry, I’m here to help. In this post, I’ll share my journey of trying to fix the issue and provide a step-by-step guide on how to secure your server with these tools.
The Problem
When you’re running Nginx Proxy Manager in Docker with Cloudflare proxy enabled, you might have noticed that Fail2Ban can’t ban attackers because all traffic appears to come from Docker’s bridge IP (172.22.0.1) instead of the real client IPs. This makes Fail2Ban useless for web protection, which is a major security concern.
The Solution
The solution lies in using a custom log format that extracts the real client IP from Cloudflare’s headers and puts it at the beginning of log lines where Fail2Ban expects to find it. This way, Fail2Ban can correctly parse the IP address and ban attackers based on their real IPs.
Step-by-Step Tutorial
Here’s a step-by-step guide on how to implement this solution:
Prerequisites:
- Fail2Ban installed and working
- Nginx Proxy Manager running in Docker
- Cloudflare proxy enabled (orange cloud) for your domains
Step 1: Create Custom Log Format
Create or edit /data/nginx/custom/http_top.conf
in your NPM data directory with the following code:
# Custom log format that puts the real client IP at the beginning
# This allows fail2ban to correctly parse the IP address
# First, map the real client IP from various sources
map $http_cf_connecting_ip $real_client_ip {
# If CF-Connecting-IP exists (Cloudflare), use it
~^(.+)$ $1;
# Otherwise fall back to X-Forwarded-For default
default $http_x_forwarded_for;
}
# Extract just the first IP if X-Forwarded-For has multiple
map $real_client_ip $client_ip_final {
# Extract first IP from comma-separated list
~^([^,]+) $1;
# If no comma, use as-is default
default $real_client_ip;
}
# Custom log format with real IP at the beginning
log_format cloudflare_real '$client_ip_final - $remote_user [$time_local] "$request" '
'"$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_cf_ray"';
Step 2: Configure Each Proxy Host
For each Cloudflare-proxied site in NPM, add the following code to the Advanced tab:
# Replace XX with your proxy host ID number
access_log /data/logs/proxy-host-XX_cloudflare.log cloudflare_real;
# Also pass real IP to backend
proxy_set_header X-Real-IP $http_cf_connecting_ip;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
Step 3: Create fail2ban Filter
Create /etc/fail2ban/filter.d/nginx-cloudflare.conf
with the following code:
[INCLUDES]
before = common.conf
[Definition]
# Match various HTTP error codes and attack patterns
failregex = ^<HOST> - .* "\w+ [^"]+" (400|401|403|404|405|444) .*$
^<HOST> - .* "\w+ (/admin|/wp-admin|/wp-login|/xmlrpc\.php|/\.env|/\.git)[^"]*" \d+ .*$
^<HOST> - .* "\w+ [^"]*(\.\.|//|\\\\)[^"]*" \d+ .*$
# Ignore successful requests to legitimate static assets
ignoreregex = ^<HOST> - .* "\w+ [^"]+" 200 .*\.css.*$
datepattern = \[{DATE}\] {^LN-BEG}
Step 4: Configure fail2ban Jail
Add the following code to /etc/fail2ban/jail.local
:
[npm-cloudflare]
enabled = true
filter = nginx-cloudflare
port = 80,443
# Monitor the custom log files with real IPs
logpath = /path/to/npm/data/logs/proxy-host-*_cloudflare.log
maxretry = 5
findtime = 600
bantime = 86400
action = iptables-multiport[name=%(__name__)s, port="%(port)s", protocol="tcp"]
Step 5: Add Cloudflare IP Whitelisting (Important!)
In your jail.local
, add Cloudflare’s IP ranges to ignoreip
:
ignoreip = 127.0.0.1/8 ::1
# Your local networks
192.168.0.0/16
10.0.0.0/8
172.16.0.0/12
# Cloudflare IPv4
173.245.48.0/20
103.21.244.0/22
103.22.200.0/22
103.31.4.0/22
141.101.64.0/18
108.162.192.0/18
190.93.240.0/20
188.114.96.0/20
197.234.240.0/22
198.41.128.0/17
162.158.0.0/15
104.16.0.0/13
104.24.0.0/14
172.64.0.0/13
131.0.72.0/22
Step 6: Restart Services
# Restart NPM
docker restart nginx-proxy-manager
# Restart fail2ban
systemctl restart fail2ban
Results
After implementing these steps, you should see real client IPs in your logs instead of the Docker bridge IP. Fail2Ban should now be able to detect and ban attackers based on their real IPs, providing better security for your server.
Example Log Output
Before (Docker bridge IP):
[11/Aug/2025:18:45:33] "GET /wp-login.php" 404 [Client 172.22.0.1]
After (Real client IP):
206.130.127.71 - - [11/Aug/2025:18:51:35] "GET /wp-login.php" 404
Bonus: Cloudflare API Integration
For even better protection, you can ban IPs at Cloudflare’s edge by creating a Cloudflare API integration. This will block attackers before they even reach your server.
Troubleshooting
If you’re still having issues, here are some troubleshooting tips:
- Still seeing 172.22.0.1? – Make sure you added the config to NPM’s Advanced tab and used the correct proxy host ID.
- Fail2Ban not detecting attacks? – Check the filter regex matches your log format with
fail2ban-regex
. - Logs not created? – Restart NPM after adding the Advanced configuration.
Naprawienie koszmaru Fail2Ban: krok po kroku przewodnik po zabezpieczaniu serwera za pomocą Nginx Proxy Manager i Cloudflare
Jeśli jesteś jak ja, to prawdopodobnie spędziłeś godziny próbując uzyskać Fail2Ban, aby działał z Nginx Proxy Manager i Cloudflare. To frustrujące doświadczenie, aby powiedzieć najmniej. Ale nie martw się, jestem tutaj, aby pomóc. W tym poście, udostępnię moją historię prób i podzielę się krok po kroku przewodnikiem, jak zabezpieczyć swój serwer za pomocą tych narzędzi.
Problem
Gdy uruchamiasz Nginx Proxy Manager w Dockerze z włączoną funkcją proxy Cloudflare, możesz zauważyć, że Fail2Ban nie może zbanować atakujących, ponieważ cały ruch wydaje się pochodzić z adresu IP mostka Docker (172.22.0.1) zamiast z rzeczywistych adresów IP klientów. To sprawia, że Fail2Ban staje się bezużyteczny dla ochrony sieci web, co jest poważnym problemem bezpieczeństwa.
Rozwiązanie
Rozwiązanie polega na użyciu niestandardowego formatu logowania, który wyodrębnia rzeczywisty adres IP klienta z nagłówków Cloudflare i umieszcza go na początku linii logów, gdzie Fail2Ban oczekuje znalezienia go. W ten sposób Fail2Ban może poprawnie sparsować adres IP i zbanować atakujących na podstawie ich rzeczywistych adresów IP.
Samouczek krok po kroku
Oto samouczek krok po kroku, jak zaimplementować to rozwiązanie:
Wymagania wstępne:
- Fail2Ban zainstalowany i działający
- Nginx Proxy Manager uruchomiony w Dockerze
- Funkcja proxy Cloudflare włączona (chmura pomarańczowa) dla Twoich domen
Krok 1: Utwórz niestandardowy format logowania
Utwórz lub edytuj /data/nginx/custom/http_top.conf
w katalogu danych NPM z następującym kodem:
# Niestandardowy format logowania, który umieszcza rzeczywisty adres IP klienta na początku
# Pozwala to Fail2Ban na poprawne sparsowanie adresu IP
# Po pierwsze, mapujesz rzeczywisty adres IP klienta z różnych źródeł
map $http_cf_connecting_ip $real_client_ip {
# Jeśli CF-Connecting-IP istnieje (Cloudflare), użyj go
~^(.+)$ $1;
# W przeciwnym razie użyj domyślnie $http_x_forwarded_for
default $http_x_forwarded_for;
}
# Wyodrębnij tylko pierwszy adres IP, jeśli X-Forwarded-For ma wiele adresów
map $real_client_ip $client_ip_final {
# Wyodrębnij pierwszy adres IP z listy przecinkowej
~^([^,]+) $1;
# Jeśli nie ma przecinka, użyj jako-is domyślnie
default $real_client_ip;
}
# Niestandardowy format logowania z rzeczywistym adresem IP na początku
log_format cloudflare_real '$client_ip_final - $remote_user [$time_local] "$request" '
'"$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_cf_ray"';
Krok 2: Konfigurowanie każdego hosta proxy
Dla każdego hosta proxy Cloudflare w NPM, dodaj następujący kod do zakładki Advanced:
# Zastąp XX z Twoim numerem ID hosta proxy
access_log /data/logs/proxy-host-XX_cloudflare.log cloudflare_real;
# Przekazuj również rzeczywisty adres IP do serwera proxy
proxy_set_header X-Real-IP $http_cf_connecting_ip;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
Krok 3: Utwórz filtr Fail2Ban
Utwórz /etc/fail2ban/filter.d/nginx-cloudflare.conf
z następującym kodem:
[INCLUDES]
before = common.conf
[Definition]
# Dopasuj różne kody błędów HTTP i wzorce ataków
failregex = ^<HOST> - .* "\w+ [^"]+" (400|401|403|404|405|444) .*$
^<HOST> - .* "\w+ (/admin|/wp-admin|/wp-login|/xmlrpc\.php|/\.env|/\.git)[^"]*" \d+ .*$
^<HOST> - .* "\w+ [^"]*(\.\.|//|\\\\)[^"]*" \d+ .*$
# Ignoruj pomyślne żądania do prawidłowych zasobów statycznych
ignoreregex = ^<HOST> - .* "\w+ [^"]+" 200 .*\.css.*$
datepattern = \[{DATE}\] {^LN-BEG}
Krok 4: Konfigurowanie więzienia Fail2Ban
Dodaj następujący kod do /etc/fail2ban/jail.local
:
[npm-cloudflare]
enabled = true
filter = nginx-cloudflare
port = 80,443
# Monitoruj niestandardowe pliki logów z rzeczywistymi adresami IP
logpath = /path/to/npm/data/logs/proxy-host-*_cloudflare.log
maxretry = 5
findtime = 600
bantime = 86400
action = iptables-multiport[name=%(__name__)s, port="%(port)s", protocol="tcp"]
Krok 5: Dodaj białą listę adresów IP Cloudflare (ważne!)
W swoim jail.local
, dodaj zakresy adresów IP Cloudflare do ignoreip
:
ignoreip = 127.0.0.1/8 ::1
# Twoje sieci lokalne
192.168.0.0/16
10.0.0.0/8
172.16.0.0/12
# Zakresy adresów IP Cloudflare
173.245.48.0/20
103.21.244.0/22
103.22.200.0/22
103.31.4.0/22
141.101.64.0/18
108.162.192.0/18
190.93.240.0/20
188.114.96.0/20
197.234.240.0/22
198.41.128.0/17
162.158.0.0/15
104.16.0.0/13
104.24.0.0/14
172.64.0.0/13
131.0.72.0/22
Krok 6: Zrestartuj usługi
# Zrestartuj NPM
docker restart nginx-proxy-manager
# Zrestartuj Fail2Ban
systemctl restart fail2ban
Wyniki
Po zaimplementowaniu tych kroków, powinieneś zobaczyć rzeczywiste adresy IP klientów w logach zamiast adresu IP mostka Docker. Fail2Ban powinien teraz być w stanie wykryć i zbanować atakujących na podstawie ich rzeczywistych adresów IP, zapewniając lepsze zabezpieczenie serwera.
Przykładowy log
Przed (adres IP mostka Docker):
[11/Aug/2025:18:45:33] "GET /wp-login.php" 404 [Client 172.22.0.1]
Po (rzeczywisty adres IP):
206.130.127.71 - - [11/Aug/2025:18:51:35] "GET /wp-login.php" 404
Bonus: Integracja z interfejsem API Cloudflare
Dla jeszcze lepszej ochrony, możesz zbanować adresy IP na skraju Cloudflare, tworząc integrację z interfejsem API Cloudflare. To zablokuje atakujących, zanim nawet dotrą do Twojego serwera.
Rozwiązywanie problemów
Jeśli nadal masz problemy, oto kilka wskazówek dotyczących rozwiązywania problemów:
- Czy wciąż widzisz 172.22.0.1? – Upewnij się, że dodałeś konfigurację do zakładki Advanced w NPM i użyłeś poprawnego numeru ID hosta proxy.
- Fail2Ban nie wykrywa ataków? – Sprawdź, czy filtr regex pasuje do Twojego formatu logowania za pomocą
fail2ban-regex
. - Logi nie są tworzone? – Zrestartuj NPM po dodaniu konfiguracji w zakładce Advanced.