Last active
July 7, 2025 22:18
-
-
Save allen-munsch/85ed55c8dbcf4b2a2999af66e0e31e90 to your computer and use it in GitHub Desktop.
install fail2ban bash script nginx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Enhanced Fail2Ban Installation Script (Idempotent) | |
# Save this as install_fail2ban.sh and run with: bash install_fail2ban.sh | |
set -e # Exit on any error | |
echo "Starting Fail2Ban installation and configuration..." | |
# Install fail2ban if not already installed | |
if ! command -v fail2ban-client &> /dev/null; then | |
echo "Installing fail2ban..." | |
sudo apt update && sudo apt install -y fail2ban | |
else | |
echo "Fail2ban already installed, updating configuration..." | |
fi | |
# Stop fail2ban to update configuration safely | |
sudo systemctl stop fail2ban 2>/dev/null || true | |
# Create main jail configuration | |
echo "Creating jail configuration..." | |
sudo tee /etc/fail2ban/jail.local > /dev/null <<'EOF' | |
[DEFAULT] | |
bantime = 3600 | |
findtime = 600 | |
maxretry = 3 | |
# Disable all default jails to avoid conflicts | |
[sshd] | |
enabled = false | |
[ssh] | |
enabled = false | |
[nginx-exploit] | |
enabled = true | |
port = http,https | |
filter = nginx-exploit | |
logpath = /var/log/nginx/access.log | |
maxretry = 1 | |
bantime = 604800 | |
findtime = 600 | |
[nginx-404] | |
enabled = true | |
port = http,https | |
filter = nginx-404 | |
logpath = /var/log/nginx/access.log | |
maxretry = 15 | |
bantime = 3600 | |
findtime = 600 | |
[nginx-badrequests] | |
enabled = true | |
port = http,https | |
filter = nginx-badrequests | |
logpath = /var/log/nginx/access.log | |
maxretry = 8 | |
bantime = 7200 | |
findtime = 600 | |
[nginx-crawlers] | |
enabled = true | |
port = http,https | |
filter = nginx-crawlers | |
logpath = /var/log/nginx/access.log | |
maxretry = 5 | |
bantime = 1800 | |
findtime = 300 | |
[nginx-forbidden] | |
enabled = true | |
port = http,https | |
filter = nginx-forbidden | |
logpath = /var/log/nginx/access.log | |
maxretry = 10 | |
bantime = 3600 | |
findtime = 600 | |
EOF | |
# Create exploit filter with fixed regex patterns | |
echo "Creating exploit filter..." | |
sudo tee /etc/fail2ban/filter.d/nginx-exploit.conf > /dev/null <<'EOF' | |
[Definition] | |
# Directory traversal and path manipulation attacks | |
failregex = ^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(%%2e%%2e|%%252e%%252e|%%%%32%%65|%%%%32%%2e|%%c0%%ae|%%c1%%9c|%%c0%%9v|%%e0%%80%%ae|\.\.\/|\.\.\\|\.\.%%2f|\.\.%%5c)" | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(\/etc\/passwd|\/etc\/shadow|\/etc\/hosts|\/etc\/group|\/etc\/fstab|\/etc\/issue|\/proc\/|\/sys\/)" | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(\/bin\/sh|\/bin\/bash|\/bin\/dash|\/bin\/zsh|\/usr\/bin\/|\/sbin\/|cmd\.exe|powershell)" | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*\/cgi-bin\/.*" | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(union\+select|union%%20select|select\+from|select%%20from|drop\+table|drop%%20table|insert\+into|insert%%20into|delete\+from|delete%%20from|update\+set|update%%20set)" | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(javascript:|vbscript:|onload=|onerror=|onclick=|onmouseover=|onfocus=|eval\(|alert\(|confirm\(|prompt\(|document\.cookie|document\.write)" | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(<script|<iframe|<object|<embed|<applet|<meta|<link|<form|<input|<img.*onerror)" | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(wget%%20|curl%%20|nc%%20|netcat|python.*-c|perl.*-e|php.*-r|ruby.*-e|bash.*-c|sh.*-c)" | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(%%00|%%0a|%%0d|%%27|%%22|%%3c|%%3e|%%3f|%%5c|%%7c|%%60|%%7b|%%7d)" | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(\/\.env|\/\.git|\/\.svn|\/\.htaccess|\/\.htpasswd|\/config\.php|\/configuration\.php|\/wp-config\.php)" | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(\/wp-admin|\/wp-login|\/wp-content\/uploads\/.*\.php|\/wp-includes\/.*\.php)" | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(\/phpmyadmin|\/pma|\/mysql|\/adminer|\/db_admin)" | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(\/manager\/|\/host-manager\/|\/server-info|\/server-status|\/balancer-manager)" | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(\.php.*\?.*\[|\.php.*\?.*\{|\.php.*\?.*\$|\.php.*\?.*eval|\.php.*\?.*system|\.php.*\?.*exec)" | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(\/api\/|\/rest\/|\/graphql|\/soap).*" 500 | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS).*(base64_decode|base64_encode|gzinflate|gzdeflate|str_rot13|strrev)" | |
ignoreregex = | |
EOF | |
# Create 404 filter | |
echo "Creating 404 filter..." | |
sudo tee /etc/fail2ban/filter.d/nginx-404.conf > /dev/null <<'EOF' | |
[Definition] | |
# Repeated 404 errors - potential scanning | |
failregex = ^<HOST> .* "(GET|POST|HEAD)" .* 404 | |
ignoreregex = ^<HOST> .* "(GET|POST|HEAD) .*\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|map)" .* 404 | |
EOF | |
# Create bad requests filter | |
echo "Creating bad requests filter..." | |
sudo tee /etc/fail2ban/filter.d/nginx-badrequests.conf > /dev/null <<'EOF' | |
[Definition] | |
# Bad HTTP requests and malformed attempts - expanded coverage | |
failregex = ^<HOST> .* ".*" (400|401|403|405|406|408|409|410|411|412|413|414|415|416|417|418|421|422|423|424|426|428|429|431|451|501|502|503|504|505|506|507|508|510|511) | |
^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS) .*HTTP\/[0-9]\.[0-9]" (400|413|414|417) | |
^<HOST> .* ".*\\x[0-9a-fA-F][0-9a-fA-F].*" | |
^<HOST> - - \[.*\] "-" 400 | |
^<HOST> - - \[.*\] ".*" 400.*".*Illegal character.*" | |
^<HOST> - - \[.*\] ".*" 400.*".*Bad Request.*" | |
^<HOST> .* ".*" 444 | |
ignoreregex = | |
EOF | |
# Create crawlers filter | |
echo "Creating crawlers filter..." | |
sudo tee /etc/fail2ban/filter.d/nginx-crawlers.conf > /dev/null <<'EOF' | |
[Definition] | |
# Aggressive crawlers and bots | |
failregex = ^<HOST> .* ".*" .* ".*bot.*" | |
^<HOST> .* ".*" .* ".*(crawler|spider|scraper|harvester|extractor).*" | |
^<HOST> .* ".*" .* ".*(sqlmap|nikto|nmap|masscan|zmap|gobuster|dirb|dirbuster|wfuzz|ffuf).*" | |
^<HOST> .* "(GET|POST|HEAD) .*" .* ".*python.*" | |
^<HOST> .* "(GET|POST|HEAD) .*" .* ".*curl.*" | |
^<HOST> .* "(GET|POST|HEAD) .*" .* ".*wget.*" | |
^<HOST> .* "(GET|POST|HEAD) .*" .* "-"$ | |
^<HOST> .* "(GET|POST|HEAD) .*" .* ""$ | |
^<HOST> .* "(GET|POST|HEAD) \/robots\.txt" | |
^<HOST> .* "(GET|POST|HEAD) \/sitemap.*\.xml" | |
ignoreregex = ^<HOST> .* ".*" .* ".*(googlebot|bingbot|slurp|duckduckbot|baiduspider|yandexbot|facebookexternalhit|twitterbot|linkedinbot|whatsapp|telegrambot).*" | |
EOF | |
# Create forbidden filter | |
echo "Creating forbidden filter..." | |
sudo tee /etc/fail2ban/filter.d/nginx-forbidden.conf > /dev/null <<'EOF' | |
[Definition] | |
# Repeated 403 Forbidden attempts | |
failregex = ^<HOST> .* "(GET|POST|HEAD|PUT|DELETE|PATCH|OPTIONS) .*" 403 | |
ignoreregex = | |
EOF | |
# Check if nginx log file exists | |
echo "Checking for required log files..." | |
if [ ! -f "/var/log/nginx/access.log" ]; then | |
echo "❌ ERROR: /var/log/nginx/access.log not found" | |
echo "Please install and configure nginx first, then run this script" | |
exit 1 | |
fi | |
echo "✅ Required log files found" | |
# Test configuration before starting | |
echo "Testing fail2ban configuration..." | |
if sudo fail2ban-client -t; then | |
echo "Configuration test passed!" | |
else | |
echo "Configuration test failed. Please check the error messages above." | |
exit 1 | |
fi | |
# Enable and start fail2ban | |
echo "Starting fail2ban service..." | |
sudo systemctl enable fail2ban | |
sudo systemctl start fail2ban | |
# Wait a moment for service to start | |
sleep 3 | |
# Show status | |
echo "Checking fail2ban status..." | |
if sudo fail2ban-client status; then | |
echo "" | |
echo "✅ Fail2ban installation complete and running!" | |
echo "" | |
echo "Useful commands:" | |
echo " Check status: sudo fail2ban-client status" | |
echo " Check specific jail: sudo fail2ban-client status nginx-exploit" | |
echo " View logs: sudo journalctl -u fail2ban -f" | |
echo " Unban IP: sudo fail2ban-client set nginx-exploit unbanip x.x.x.x" | |
else | |
echo "❌ Fail2ban is not responding properly. Check logs with:" | |
echo "sudo journalctl -u fail2ban -n 50" | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment