fail2ban-swag
fail2ban + SWAG Integration Skill
⚠️ MANDATORY SKILL INVOCATION ⚠️
YOU MUST invoke this skill (NOT optional) when the user mentions ANY of these triggers:
- "check fail2ban", "fail2ban status", "f2b status", "banned IPs"
- "ban an IP", "unban an IP", "block IP address", "IP blacklist"
- "whitelist an IP", "whitelist IP address", "add to ignoreip"
- "create a jail", "add fail2ban jail", "configure jail"
- "test fail2ban filter", "regex test", "filter testing"
- "monitor fail2ban", "fail2ban logs", "intrusion detection"
- "troubleshoot fail2ban", "why isn't fail2ban working", "bans not working"
- Any mention of SWAG security, intrusion prevention, or IP blocking
Failure to invoke this skill when triggers occur violates your operational requirements.
Purpose
Manage fail2ban intrusion prevention system running inside the SWAG (Secure Web Application Gateway) reverse proxy container on remote host. This skill provides tools for creating jails, configuring filters, monitoring activity, troubleshooting issues, and maintaining security for the reverse proxy infrastructure.
Operations supported:
- ✅ Read-Write (Safe): Create jails, configure filters, reload fail2ban
- ✅ Read-Only: Monitor status, view logs, inspect configurations
- ⚠️ Destructive (with confirmation): Unban IPs, delete jails
Setup
CRITICAL CONTEXT: All operations execute inside the SWAG container. Do NOT attempt to run fail2ban commands on the host.
Environment Configuration:
The script requires the following environment variables (can be set in .env or exported):
# Remote host SSH configuration
SWAG_HOST="your-hostname" # Hostname or IP of the server running SWAG
# Container configuration
SWAG_CONTAINER_NAME="swag" # Name of the SWAG container (default: swag)
# Path configuration
SWAG_APPDATA_PATH="/path/to/appdata/swag" # Path to SWAG appdata directory
Example .env file:
# fail2ban-swag configuration
SWAG_HOST="homelab.local"
SWAG_CONTAINER_NAME="swag"
SWAG_APPDATA_PATH="/mnt/appdata/swag"
Typical Environment:
- Host: Remote server with SSH access
- Container: swag (LinuxServer.io SWAG image)
- Container Network: Docker bridge network with unique IP
- Ports: 80 (HTTP), 443 (HTTPS)
- Compose Location:
/path/to/compose/swagor similar - Appdata Location:
/path/to/appdata/swag(configurable)
Key Characteristics:
- fail2ban runs INSIDE the SWAG container (not on host)
- Requires
NET_ADMINcapability for iptables manipulation - Uses DOCKER-USER iptables chain for container protection
- All configuration persists via bind mounts to appdata directory
Common Jails:
- nginx-http-auth (HTTP Basic Auth failures)
- nginx-badbots (malicious User-Agents)
- nginx-botsearch (vulnerability scanners)
- nginx-deny (nginx access rule violations)
- nginx-unauthorized (HTTP 401 responses)
Typical Ban Policy:
- Ban duration: 1 hour (3600 seconds)
- Detection window: 60 seconds
- Max retries: 5 attempts (2 for badbots)
- Database purge: 1 day retention
Typical Whitelisted Networks:
- 10.0.0.0/24, 192.168.0.0/24 (Private LANs)
- 172.16.0.0/12 (Docker networks)
- Your external admin IP (e.g., 203.0.113.10)
Commands
All commands use the wrapper script which handles SSH execution inside the SWAG container.
Setup
Ensure script is executable:
chmod +x scripts/fail2ban-swag.sh
Test connection:
./scripts/fail2ban-swag.sh status
JSON Output
Add --json flag before any command for machine-readable output:
./scripts/fail2ban-swag.sh --json status
./scripts/fail2ban-swag.sh --json jail-status nginx-http-auth
./scripts/fail2ban-swag.sh --json list-jails
./scripts/fail2ban-swag.sh --json banned-ips nginx-unauthorized
Core Operations
View fail2ban status:
./scripts/fail2ban-swag.sh status
List all jails:
./scripts/fail2ban-swag.sh list-jails
Check specific jail status:
./scripts/fail2ban-swag.sh jail-status nginx-http-auth
View currently banned IPs:
./scripts/fail2ban-swag.sh banned-ips nginx-http-auth
Unban an IP address:
./scripts/fail2ban-swag.sh unban 192.168.1.100
# Or from specific jail:
./scripts/fail2ban-swag.sh unban 192.168.1.100 nginx-http-auth
Ban an IP manually:
./scripts/fail2ban-swag.sh ban 192.168.1.100 nginx-http-auth
Reload fail2ban (after config changes):
./scripts/fail2ban-swag.sh reload
Test filter regex against logs:
./scripts/fail2ban-swag.sh test-filter nginx-http-auth
View fail2ban logs:
./scripts/fail2ban-swag.sh logs
# Or tail live:
./scripts/fail2ban-swag.sh logs --follow
View nginx logs:
./scripts/fail2ban-swag.sh nginx-access-log
./scripts/fail2ban-swag.sh nginx-error-log
Search logs for specific IP:
./scripts/fail2ban-swag.sh search-ip 192.168.1.100
View iptables rules:
./scripts/fail2ban-swag.sh iptables
Advanced Operations
Create a custom jail:
./scripts/fail2ban-swag.sh create-jail custom-jail \
--filter custom-filter \
--logpath "/config/log/nginx/access.log" \
--maxretry 5 \
--findtime 600 \
--bantime 3600
Create a custom filter:
./scripts/fail2ban-swag.sh create-filter custom-filter \
--regex '^<HOST>.*"(GET|POST).*" (403) .*$'
Edit jail configuration:
./scripts/fail2ban-swag.sh edit-jail nginx-http-auth
Backup current configuration:
./scripts/fail2ban-swag.sh backup
Restore configuration:
./scripts/fail2ban-swag.sh restore backup-2026-02-07.tar.gz
Workflow
When user asks about fail2ban status:
- "Check fail2ban status" → Run
./scripts/fail2ban-swag.sh statusto show overview - "Show banned IPs" → Run
./scripts/fail2ban-swag.sh list-jailsthen check each jail with./scripts/fail2ban-swag.sh banned-ips <jail> - "Is IP X.X.X.X banned?" → Run
./scripts/fail2ban-swag.sh search-ip X.X.X.X
When user wants to unban an IP:
- Confirm the IP to unban
- Run
./scripts/fail2ban-swag.sh unban X.X.X.X - Verify unbanned with
./scripts/fail2ban-swag.sh search-ip X.X.X.X
When user wants to create a custom jail:
- Understand the attack pattern → Ask what log entries indicate the attack
- Create filter → Run
./scripts/fail2ban-swag.sh create-filter <name> --regex '<pattern>' - Test filter → Run
./scripts/fail2ban-swag.sh test-filter <name>to verify matches - Create jail → Run
./scripts/fail2ban-swag.sh create-jail <name>with appropriate parameters - Reload fail2ban → Run
./scripts/fail2ban-swag.sh reload - Monitor activity → Run
./scripts/fail2ban-swag.sh jail-status <name>to verify detections
When troubleshooting bans not working:
- Check jail status → Verify jail is enabled and running
- Test filter → Ensure regex matches log entries
- Check iptables → Verify rules are being created in DOCKER-USER chain
- Review logs → Check fail2ban.log for errors
- Verify NET_ADMIN → Ensure container has capability to modify iptables
When user reports false positives:
- Identify the IP → Confirm which IP is being incorrectly banned
- Check which jail → Determine which jail triggered the ban
- Add to whitelist → Either add IP to ignoreip in jail.local or adjust filter
- Reload fail2ban → Apply changes with
./scripts/fail2ban-swag.sh reload
Notes
Architecture
Container Integration:
- fail2ban runs as a service inside the SWAG container
- Uses host networking mode for iptables access
- Configuration bind-mounted from
${SWAG_APPDATA_PATH}/fail2ban/ - Logs bind-mounted from
${SWAG_APPDATA_PATH}/log/fail2ban/
iptables Chain:
- CRITICAL: Uses
DOCKER-USERchain (NOTINPUT) INPUTchain does NOT work for containerized applications- DOCKER-USER chain evaluated before Docker's NAT rules
- All jails must specify
chain = DOCKER-USERin configuration
Real IP Detection:
- nginx configured with
realipmodule - Essential when behind CDN (Cloudflare, etc.)
- Without real IP restoration, only CDN IPs get banned
Security Considerations
Whitelisting:
- Always whitelist management networks in
ignoreip - Be cautious with VPN/proxy ranges (may allow attackers)
- External admin IP should be rotated if changed
Ban Duration:
- 1 hour default is moderate
- Consider longer for repeat offenders (recidive jail)
- Permanent bans require manual iptables rules
Attack Vectors Protected:
- HTTP Basic Auth brute force (nginx-http-auth)
- Credential stuffing (nginx-unauthorized)
- Vulnerability scanning (nginx-botsearch)
- Bot/scraper activity (nginx-badbots)
- Access rule violations (nginx-deny)
NOT Protected (requires custom jails):
- Application-layer attacks (SQL injection, XSS)
- Rate limiting bypass
- DDoS attacks
- Zero-day exploits
Performance
ipset Optimization:
- Consider enabling ipset for high ban counts
- O(1) hash lookup vs O(n) for standard iptables
- Requires ipset support in kernel
Log Parsing:
- fail2ban scans logs every 5 seconds by default
- Large log files can impact performance
- Consider log rotation for nginx (currently 14 days)
Troubleshooting
Common Issues:
-
Bans not working
- Wrong iptables chain (check DOCKER-USER)
- Missing NET_ADMIN capability
- Timezone mismatch between logs and fail2ban
- Whitelisted IP in ignoreip
-
Filter not matching
- Regex syntax error
- Log format changed (nginx update)
- Multiline log entries (need special handling)
-
High false positive rate
- Threshold too strict (lower maxretry)
- Detection window too short (increase findtime)
- Legitimate traffic patterns misidentified
-
fail2ban not starting
- Configuration syntax error
- Missing log files
- Permission issues
- Socket file conflicts
Debugging Steps:
- Check fail2ban process:
docker exec swag ps aux | grep fail2ban - Test filter regex:
./scripts/fail2ban-swag.sh test-filter <name> - Verify iptables rules:
./scripts/fail2ban-swag.sh iptables - Review logs:
./scripts/fail2ban-swag.sh logs --follow - Validate configuration:
docker exec swag fail2ban-client -t
Reference
Research Documentation:
- See
../docs/research/swag-fail2ban-integration/for detailed setup documentation and research findings
Official Documentation:
Additional References:
- references/quick-reference.md - Quick command examples for common operations
- references/filter-examples.md - Pre-built filter patterns for common attack types
- references/troubleshooting.md - Detailed troubleshooting procedures