Skip to main content

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/ or public/ 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.