Nginx Proxy Manager (NPM) gained popularity as reverse proxy amongst selfhosters and homelabbers due to its simplicity for managing SSL certificates and proxy hosts via a web interface.
It also supports a simplistic feature for proxy hosts called
block common exploits that will block query parameters containing well-known payloads for exploiting vulnerabilities such as SQL injections (SQLi), Cross-Site Scripting (XSS), Remote and Local File Inclusion (RFI/LFI) as well as other hacking techniques.
Combined with the advanced Web Application Firewall (WAF) and Denial-of-Service (DoS) protection by Cloudflare, many attack vectors can be blocked straight away. Nonetheless, we can improve such a setup even more by implementing Fail2ban as additional Intrusion Detection (IDS) and Prevention System (IPS). Since all proxy host log files of NPM are structured in a normalized format, Fail2ban can easily be used to monitor those logs for malicious activities such as forceful browsing or brute-forcing.
This also works when not using Cloudflare at all. So keep reading.
For this blog post, we will focus on implementing Fail2ban in conjunction with NPM as reverse proxy behind Cloudflare. I will not go into detail on how to run NPM, configure port forwardings on your router or define the necessary DNS entries on Cloudflare to get you started. I assume that these things are already running and working for you.
This blog post will only focus on installing and configuring a dockerized Failban container to work with your NPM reverse proxy logs. Additionally, we will configure optional Telegram notifications on actual IP bans and unbans.
If you are not using Cloudflare, don't worry. You can follow this blog post and neglect the relevant things for Cloudflare just fine. Your main focus will then be on banning threat actors using iptables only. Note that many modern Linux distributions nowadays are shipped with nftables instead of iptables. As we rely on iptables and specifically on the string matching extension, not supported by nftables, you'll have to ensure that iptables is installed on your server and that your kernel supports the string matching feature.
This ensures that the logs of NPM contain the correct IP address of your site visitors. Cloudflare specifies the real visitor's IP address in a custom HTTP header called
Fun fact: NPM propagates the file
/etc/nginx/conf.d/include/ip_ranges.confautomatically during run with IP addresses of popular CDN networks such as Cloudflare. This happens intransparently but ensures that only trustworthy IPs can set the
X-Real-IPheader that is processed by NPM to get the real IP address of site visitors.
Our goal will be to identify and block threat actors that actively conduct forceful browsing and brute-forcing attacks on our web services. Forceful browsing is the act of enumerating a web service for sensitive files, interesting directories and potentially vulnerable endpoints in an automated manner. Moreover, some threat actors may also try brute-forcing login areas or Basic Authentication prompts.
Since all these potential hacking attempts will generate log entries with 401, 403, 404 HTTP errors in a very short time period, we can easily identify such attacks and ban the threat actor's IP address via Fail2ban locally and globally on Cloudflare. Fail2ban will monitor all proxy host logs of our Nginx Proxy Manager reverse proxy. Therefore, once a misbehaving threat actor is detected and banned by Fail2ban, the attacker won't be able to access any of our other web services subsequently. This applies to all configured proxy hosts in NPM. Basically a universal ban as soon as one hacking attempt is detected on one of our HTTP web services.
Running Fail2ban with Docker Compose
Fail2ban can be run and installed on bare metal, but there also exists a containerized version. For this blog post, we will focus on using a Docker container provided by crazymax.
You can use the following docker-compose file to spawn a dockerized Fail2ban container. Please adjust your volume mappings and environment variables if needed.
/data/logsof your NPM reverse proxy into the Fail2ban docker container at
/var/log/npm. Otherwise, Fail2ban is not able to inspect your NPM logs!
Afterwards, start the Docker container with the following command:
Upon starting our Fail2ban Docker container, we will notice four new folders at the persisted data storage we defined in the above
These folders are necessary and used to define our actual Fail2ban configuration in order to detect malicious behavior in log files as well as ban the IP address of misbehaving threat actors. Note that the
db folder can be ignored for now since it only holds the sqlite3 database for Fail2ban.
Put the following configuration file called
jail.local inside the
jail.d directory. This is our main configuration file for Fail2ban. It defines various settings for our jails such as which log files to monitor and when as well as how long a threat actor should be banned. Adjust to your needs and liking. Consult the Fail2ban documentation for more details regarding configuration options and syntax.
action-ban-cloudflarereference from the
action =directive. Also remove whitelisting of Cloudflare IP addresses at
ignoreipparameter. This ensures that we are not banning ourselves or our CDN provider by accident.
Remove the Cloudflare IP addresses if you do not plan on using Cloudflare as proxy. If Cloudflare is not in use, only define
ignoreip = 127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16.
Put the following configuration file called
npm-general-forceful-browsing.conf inside the
filter.d directory. This configuration file is used to define which NPM log entries are relevant for Fail2ban to monitor and act on:
failregexparameter specifies a typical log entry format of failed HTTP requests. If such entries occur multiple times, it is usually an indicator of an ongoing forceful browsing or brute-forcing attack.
ignoreregexparameter is used to ignore specific log entries, e.g. static or media files that cannot be found (404) on the server.
By using fail2ban's failregex notiation
<F-*>...</F-*>we can even introduce custom variables. This allows us to retrieve e.g. the container name the HTTP request was sent to or to obtain the threat actor's user agent string. These variables may later be used when sending Telegram notifications or email alerts.
Finally, put the following two configuration files inside the
The first configuration file is used to ban threat actors on Cloudflare itself using the Cloudflare API. This is recommended if you are using Cloudflare as CDN provider with the orange cloud symbol enabled. If not, just neglect this action.d script at all and remove the
action-ban-cloudflare action reference in the
jail.local configuration file above.
If you will use Cloudflare as proxy, please adjust the below configuration file and define your Cloudflare credentials at the variables
cfuser. Use your account's email address and your
Global API Key available at https://dash.cloudflare.com/profile/api-tokens.
Additionally, use the following configuration file to ban the threat actor's IP address via
iptables on your server directly. This is optional when using Cloudflare as proxy with the orange cloud symbol enabled, but mandatory if you are not using Cloudflare at all.
action-ban-docker-forceful-browsing.conf, you have to ensure that iptables version >= 1.3.5 is installed on your system as well as that your kernel supports string matching. May read here.
Note: Newer OS versions seem to use nftables only. Since nftables do not support string matching, your IP bans via fail2ban are not effective. Install and configure legacy iptables if you want to use this feature. Otherwise, you have to use Cloudflare and its API for banning.
You can list kernel modules with the command
ls /lib/modules/`uname -r`/kernel/net/netfilter/and search for the required
xt_string.kokernel module. If you do not get a hit, your kernel seems not yet to support iptables' string matching extension.
Note that we have to use the
DOCKER-USER chain, since we are using Docker containers only. In detail, we will utilize the iptable's netfilter extension to ban the real IP address of a threat actor in the
X-Forwarded-For header. Iptables can only see the source IP address of packets, namely the IP of your proxy, but not the real visitor's IP address commonly defined in HTTP headers like
If all configuration files are set up, please restart the Fail2ban Docker container to reflect all changes. For example via the following bash command:
Configuring Telegram Notifications
Note that you can specify multiple
actionunban actions. Currently, we only ban a misbehaving IP address of a threat actor via iptables and optionally via the Cloudflare API. However, we can also implement additional Telegram notifications when actual IP bans or unbans occur.
For this, append an additional line of code at the
actionunban definitions, which calls a shell script that sends Telegram notifications. As an example, a proper configuration should then look like this:
The corresponding bash script to send notifications via your Telegram bot can be obtained via the following file download. Place the script within the
action.d directory and define your Telegram API token and chat ID inside the script.
Testing our Setup
Finally, we should test our Fail2ban setup for proper configuration and that it really works. Head over to your mobile phone, disconnect from your local Wi-Fi network and start using a mobile LTE connection. This ensures that your soon to be made requests are not originating from a whitelisted IP address within the
ignoreip parameter of the
jail.local Fail2ban configuration file. Furthermore, we want to ensure that you do not lock yourself out with your real WAN IP address.
Then proceed by accessing a publicly exposed service via your mobile web browser. Start manipulating the browser URL and request a non-existent directory or file to trigger a
404 Not Found HTTP error. Proceed with these requests (at least 15 times within 1 minute since this is the current threshold defined in Fail2ban) until you notice a ban of your IP address.
You should not be able to access any web service proxied by NPM anymore. Your browser should also display an error page by Cloudflare (if Cloudflare as proxy is in use) notifying you that your IP address was blocked. This applies to all proxy hosts of your NPM instance. You won't be able to access any HTTP web service for 10 minutes, until you will be automatically unbanned by Fail2ban again.
You may also consult the Fail2ban Docker logs for more details. You should see log entries notifying you about an IP address (your LTE IP) behaving maliciously. You may also login at Cloudflare if used and inspect the WAF or firewall rules to confirm an actual IP ban on the Cloudflare network for your managed domain.
To unban yourself, either wait 10 minutes or use the following bash command inside the Fail2ban docker container:
To test your Fail2ban filters, you can execute the following commands inside the Fail2ban Docker container: