
Blocking Hacking Attack Vectors with Nginx and Fail2ban
Nginx Configuration Example to Block Unknown Routes and Log:
server {
listen 80 default_server;
server_name _;
root /opt/una;
client_max_body_size 200M;
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
index index.php index.html index.htm;
# Known routes
location / {
index index.php index.html index.htm;
rewrite "^/page/(.*)$" /page.php?i=$1 last;
rewrite "^/m/(.*)$" /modules/index.php?r=$1 last;
rewrite "^/s/([a-zA-Z0-9_]+)/([a-zA-Z0-9\.]+)" /storage.php?o=$1&f=$2 last;
if (!-e $request_filename) {
rewrite ^/(.+)$ /r.php?_q=$1 last;
break;
}
}
location ~ \.php$ {
fastcgi_pass php:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Allow /.well-known for Certbot or similar tools
location ~ ^/\.well-known/ {
allow all;
}
# Block everything else — log and return 403
location / {
access_log /var/log/nginx/forbidden_access.log main;
return 403;
}
}
Fail2ban Configuration
Filter: /etc/fail2ban/filter.d/nginx-forbidden.conf
[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD).*(HTTP.*)" 403
ignoreregex =
Jail Configuration: /etc/fail2ban/jail.local
[nginx-forbidden]
enabled = true
port = http,https
filter = nginx-forbidden
logpath = /var/log/nginx/forbidden_access.log
maxretry = 3
bantime = -1 # Permanent ban
findtime = 300
action = iptables[name=HTTP, port=http, protocol=tcp]
Restart and Check Fail2ban
sudo systemctl restart fail2ban sudo fail2ban-client status
Check jail status:
sudo fail2ban-client status nginx-forbidden
Test the Configuration
Run the following command to test:
curl http://yourserver/random/unknown/path
You should see in /var/log/nginx/forbidden_access.log
:
1.2.3.4 - - [28/Apr/2025:13:00:00 +0000] "GET /random/unknown/path HTTP/1.1" 403 150 "-" "curl/7.88.1"
After 3 attempts, Fail2ban will block the IP.
Check with:
sudo fail2ban-client status nginx-forbidden
Now:
- Nginx handles known routes
- Logs unknown routes separately
- Fail2ban bans IPs after 3 attempts in 5 minutes
- IP gets blocked permanently via iptables
NOTE:
This is just an example that should be configured based on your site to block unauthorized access attempts using Fail2ban. It can help mitigate hacking attempts.
You can add more recipes (filters and jails) in Fail2ban as needed. These can be tailored to different types of access attempts or security events on your system.
For example, you can create custom Fail2ban filters to detect specific attack patterns, such as:
- SSH Brute Force - For blocking repeated login attempts via SSH.
- Nginx 404 or 403 Errors - To block unauthorized or suspicious access attempts that trigger certain HTTP status codes.
- Custom Application Logs - If you have custom web applications, you can create filters that target specific error patterns in the application logs.
You can add these by following the same structure as shown in the previous example. Here’s how you can extend your Fail2ban setup:
Example of Adding a New Jail
1. Custom Filter Example: /etc/fail2ban/filter.d/nginx-404.conf
[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD).*(HTTP.*)" 404
ignoreregex =
2. New Jail Configuration: /etc/fail2ban/jail.local
Add this section to your jail.local
file to handle the 404 errors:
[nginx-404]
enabled = true
port = http,https
filter = nginx-404
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 3600
findtime = 600
action = iptables[name=HTTP, port=http, protocol=tcp]
This configuration will block any IP after 5 failed attempts to access a non-existent page, and it will be banned for 1 hour.
3. Restart Fail2ban
After adding the new filter and jail, restart Fail2ban:
sudo systemctl restart fail2ban
You can keep adding more recipes based on your needs and what kind of attack vectors you're trying to block.
4. Fail2ban repo:
⚠️ IMPORTANT NOTICE
- 🚨 These commands and configuration changes are for informational purposes or development.
- 💾 They DO NOT represent an official setup guide.
- ⚙️ Run them ONLY if you fully understand what they do.
- ❗ Use at your own risk. Adjust if necessary.
- 👉 This example is intended for advanced users only.