Fail2Ban
How I Protected your site from Scanners Using NGINX + Fail2Ban
When I first deployed my Speech Coach Telegram bot — a tool to practice spoken English — everything seemed to work fine. Until I opened the NGINX access logs.
There were hundreds of suspicious requests: .env
, .git
, admin.php
, wp-login.php
. Classic vulnerability scanners. Even a small side project online is a target.
So I decided to secure it — fast.
Step 1: Locking Things Down with NGINX
NGINX isn’t just a reverse proxy — it can be a solid first layer of defense.
1. IP Whitelisting for Telegram Webhook
location /webhook/ {
proxy_pass http://host.docker.internal:8000/webhook/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
allow 149.154.160.0/20; # Telegram IP
allow 91.108.4.0/22; # Telegram IP
allow 192.168.1.0/22; # Local testing
deny all;
error_page 403 = @denied;
}
location @denied {
return 403 "⛔ Access Denied.";
}
Only Telegram (and my local machine) can hit the webhook now.
2. Serving Frontend Safely
If you're serving a static frontend (like a built Next.js/React app), never point NGINX to the project root. Sensitive files like .git
, .env
, or source code might be exposed.
Correct way:
location / {
root /app/build;
index index.html;
try_files $uri /index.html;
}
3. Blocking Hidden and Temp Files
Here’s a basic hardening set:
# Block .env, .git, .htaccess, etc.
location ~ /\.(?!well-known).* {
deny all;
}
# Block backups and temp files
location ~* \.(bak|swp|tmp|log|sql|old|orig)$ {
deny all;
}
This is a must-have in any public-facing NGINX config.
Step 2: Auto-Banning Scanners with Fail2Ban
NGINX blocks access, but scanners can keep hammering your server. That’s where Fail2Ban comes in — it monitors logs and bans malicious IPs system-wide.
Sample filter /etc/fail2ban/filter.d/nginx-badbots.conf
[Definition]
failregex = ^<HOST> - - \[.*\] "(GET|POST|HEAD) .*(\.env|\.git|admin|login).* HTTP/.*" (403|404|405)
ignoreregex =
ignoreip =
Jail config
[nginx-badbots]
enabled = true
filter = nginx-badbots
action = iptables[name=BadBots, port=http, protocol=tcp]
logpath = /var/log/nginx/access.log
findtime = 600
bantime = 3600
maxretry = 3
After just 30 minutes of running this setup, dozens of scanning IPs were blocked automatically. Logs were cleaner, and the server ran more smoothly.
Takeaways
- ✅ Use
allow/deny
in NGINX to limit critical endpoints. - 🔒 Never expose your project root. Point to
build/
orpublic/
only. - 🚫 Block common sensitive files and backup extensions.
- 🧱 Use Fail2Ban to automatically shut down bad actors.
- 📈 For extra visibility, consider logging with Grafana + Loki.
This setup only took a few hours but significantly increased the project’s stability and security — and gave me peace of mind.