Skip to content

Instantly share code, notes, and snippets.

@allen-munsch
Last active July 7, 2025 22:18
Show Gist options
  • Save allen-munsch/85ed55c8dbcf4b2a2999af66e0e31e90 to your computer and use it in GitHub Desktop.
Save allen-munsch/85ed55c8dbcf4b2a2999af66e0e31e90 to your computer and use it in GitHub Desktop.
install fail2ban bash script nginx
#!/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