This post will teach you how to setup Fail2Ban with Nginx and the reverse proxy of Cloudflare. In my case I’ll be using phpMyAdmin as an example.
Get the original visitors IP address
First we need to make sure Nginx knows the real IP of the visitor to make your bans work. If you don’t set this up Nginx will simple see the Cloudflare IP. Even If you don’t plan to use Fail2Ban you should set this up.
First check If jp and crontab are installed on your system. We’ll be using cronjob to get the Cloudflare IPs and keep it up to date and Nginx’s module nginx_http_realip_module to reveal to original visitors IP. You can also find this script on GitHub.
Setup the services on your server
First create a new file wich will get the current Cloudflare IPs and automaticly update these, they might change from time to time. You can choose a different location for your script.
sudo nano /home/cf-update_ips.sh
#!/bin/bash
cf_ips="$(curl -fsLm2 --retry 1 https://api.cloudflare.com/client/v4/ips)"
CLOUDFLARE_FILE_PATH=${1:-/etc/nginx/cloudflare}
echo "# Cloudflare IP Ranges" > $CLOUDFLARE_FILE_PATH
echo "" >> $CLOUDFLARE_FILE_PATH
echo "# - IPv4" >> $CLOUDFLARE_FILE_PATH
for ipv4 in $(echo "$cf_ips" | jq -r '.result.ipv4_cidrs[]//""' | sort); do
echo "set_real_ip_from $ipv4;" >> $CLOUDFLARE_FILE_PATH
done
echo "" >> $CLOUDFLARE_FILE_PATH
echo "# - IPv6" >> $CLOUDFLARE_FILE_PATH
for ipv6 in $(echo "$cf_ips" | jq -r '.result.ipv6_cidrs[]//""' | sort); do
echo "set_real_ip_from $ipv6;" >> $CLOUDFLARE_FILE_PATH
done
echo "" >> $CLOUDFLARE_FILE_PATH
echo "real_ip_header CF-Connecting-IP;" >> $CLOUDFLARE_FILE_PATH
nginx -t && systemctl reload nginx
Set the file premission to 755 to execute your script.
sudo chmod 755 /home/cf-update_ips.sh
Now create the cronjob automaticly get the Cloudflare IPs.
# Get Cloudflare IPs and reload Nginx
# Note: 4 am
0 4 * * * /home/cloudflare.sh >/dev/null 2>&1
Now open your Nginx config file with sudo nano /etc/nginx/nginx.conf and add the following line to the http block:
include /etc/nginx/cloudflare;
For every service / website you’re reverse-proxying add the line inside the location { block:
include proxy_params;
When you are done editing run sudo nginx -t to verify your configs and restart Nginx with sudo service nginx restart. Nginx should now restore the original IP of the visitor coming through Cloudflare proxy.
Setup Cloudflare
A free Cloudflare account is limited to 5 WAF rules per zone but you can create one custom list that can store up to 10.000 IPs, wich is more than enough.
First create a new custom list, you can use the offical instructions by Cloudflare. You can choose a suitable name for your list, it dosn’t need a specific name. Pay attention that “Type” is set to “IP”. Select “Create” and check the URL, it will be in the following format: https://dash.cloudflare.com/<account-id>/configurations/lists/<list-id>/add Note down the values in account-id and list-id, we need these later.
You’ll also need an API token for this setup. Follow these instructions. We will use a custom token, with the premissions Account”, “Account Filter Lists”, and “Edit”. Note down the API token.
Now create a new WAF rule. Use these these instructions. Set “Field” to “IP Source Address” and “Operator” to “is in list”. Your list should automatically be selected. Set “Action” to “Block” and “Order” to “First”. Now your Cloudflare setup is complete.
Setup Fail2Ban
Make sure you installed jp in your system.
We are going to use the ban action from @sebres. It supports IPv4 and IPv6 addresses. Create the file sudo nano /etc/fail2ban/action.d/cloudflare-list.conf with the following content:
[Definition]
actionban = curl -s -o /dev/null -X POST <_cf_api_prms> \
-d '[{"ip":"'"<cfip>"'","comment":"Created by fail2ban <name>"}]' \
<_cf_api_url>
actionunban = id=$(curl -s -X GET <_cf_api_prms> \
"<_cf_api_url>?search=<cfip>&per_page=1" \
| { jp --unquoted 'result[0].id | not_null(@, `""`)' 2>/dev/null; })
if [ -z "$id" ]; then echo "<name>: id for <ip> cannot be found"; exit 0; fi;
curl -s -o /dev/null -X DELETE <_cf_api_prms> \
-d '{"items":[{"id":"'"$id"'"}]}' \
<_cf_api_url>
_cf_api_url = https://api.cloudflare.com/client/v4/accounts/<cfaccountid>/rules/lists/<cfbanlistid>/items
_cf_api_prms = -H 'Authorization: bearer <cfapitoken>' -H 'Content-Type: application/json'
[Init]
cfip = <ip>
[Init?family=inet6]
cfip = $(fail2ban-python -c 'import sys; from fail2ban.server.ipdns import IPAddr; a = IPAddr(sys.argv[1]+"/"+sys.argv[2]); print(str(a))' "<ip>" 64)
Now create the file that contains your API token and your ban list sudo nano /etc/fail2ban/action.d/cloudflare-list.local with the following content:
[Init]
cfapitoken = <api-token>
cfaccountid = <account-id>
cfbanlistid = <list-id>
Replace the placeholders with your credentials and make sure the file premission is set to 640 because it contains secret informations sudo chmod 640 /etc/fail2ban/action.d/cloudflare-list.local so only the root user can acces it.
Now for example let’s configure Fail2Ban for phpMyAdmin. Enable logging in phpMyAdmin by adding the following lines to the server parameters section:
$cfg['AuthLog'] = '/var/log/phpmyadmin.log';
$cfg['AuthLogSuccess'] = true;
Create the file sudo touch /var/log/phpmyadmin.log and give it the premission 777 (sudo chmod 777 /var/log/phpmyadmin.log).
Create sudo nano /etc/fail2ban/filter.d/phpmyadmin.conf and add the configuration:
# Fail2ban - phpMyAdmin Filter
[INCLUDES]
before = common.conf
[Definition]
_daemon = phpMyAdmin
failregex = ^%(__prefix_line)suser denied: (?:\S+|.*?) \(mysql-denied\) from <HOST>\s*$
ignoreregex =
Now create the Jail sudo nano /etc/fail2ban/jail.d/phpmyadmin.conf and add:
[phpmyadmin]
enabled = true
port = http,https
logpath = /var/log/phpmyadmin.log
action = cloudflare-list nginx-block-map
You might set chain = forward depending on how you configurated Fail2Ban. Reminder that we are not banning a IP address, we are just blocking it using Cloudflare firewall rules.
Setup Nginx
Add the following line to the http block in your Nginx main config:
map $remote_addr $ip_blacklisted { include blacklisted-sessions.map; }
Add the following line to the server block of services you’re reverse-proxying:
if ($ip_blacklisted) { return 444; }
Note: You might need to restart Nginx and create the blacklisted-sessions.map by hand:
sudo touch /etc/nginx/blacklisted-sessions.map
sudo service nginx restart
Conclusion
Now your IPs who fail to authenticate should be send to Cloudflares firewall and get blocked.
This guide was last updated on December 18, 2025.