Having both build: and image: caused Docker Compose to attempt a registry pull before building locally. Drop image: so it always builds from source. Remove obsolete version: field. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
F2B Control Center
A batteries-included Fail2Ban distribution for Nginx Proxy Manager environments, packaged as a single Docker container.
Combines Fail2Ban (active blocking), a web dashboard (monitoring + management), and pre-built detection rules tailored for NPM reverse proxy logs — all with a minimal setup process.
Features
- Live ban feed — real-time stream of Fail2Ban ban events
- Jail management — view, ban, unban, and whitelist IPs across all jails
- Nginx log scanner — identify suspicious IPs from access logs before they trigger automatic rules
- AbuseIPDB integration — look up IP reputation scores; auto-ban by threshold
- Ban history — persistent record of all banned IPs with ban counts and timestamps
- Pre-built filters for NPM logs: bad bots, HTTP error spamming, exploit probing
- Webhook support — POST ban events to any HTTP endpoint (Discord, Slack, n8n)
- Cloudflare sync hook — optional script to sync whitelist with CF firewall rules
Requirements
- Docker Engine 20.10+
- Docker Compose v2+
- Nginx Proxy Manager running and producing access logs
- Linux host with iptables support (for active banning)
Quick Start
1. Clone the repository
git clone http://YOUR_GITEA_HOST/YOUR_USER/f2b-control-center.git
cd f2b-control-center
2. Configure your environment
cp .env.example .env
Edit .env and set at minimum:
# Path to your NPM log directory on the host
NPM_LOG_DIR=/home/docker/NGINX/data/logs
# AbuseIPDB API key (optional but strongly recommended)
ABUSEIPDB_API_KEY=your_key_here
3. Start the container
docker-compose up -d
4. Open the dashboard
Navigate to http://YOUR_HOST:4000 in your browser.
On first start, the container will install the default Fail2Ban configuration into a persistent Docker volume. Fail2Ban will begin monitoring your NPM logs immediately.
Configuration Reference
All settings are controlled via .env and the docker-compose.yml environment section.
| Variable | Default | Description |
|---|---|---|
NPM_LOG_DIR |
/opt/npm/data/logs |
Host path to NPM access log directory |
DASHBOARD_PORT |
4000 |
Port the dashboard listens on |
ABUSEIPDB_API_KEY |
(empty) | AbuseIPDB API key — enables threat scoring |
AUTOBAN_THRESHOLD |
75 |
Abuse score threshold for auto-ban (0–100) |
DEFAULT_LOOKBACK_DAYS |
3 |
Default scan window for nginx logs |
SUBNETS_TO_IGNORE |
10.0.0.0/8,... |
Comma-separated CIDRs to ignore in scans |
WEBHOOK_URL |
(empty) | HTTP endpoint to notify on manual bans |
Network Mode
The container runs with network_mode: host by default.
This is intentional. For Fail2Ban's iptables rules to block traffic arriving at the host before it reaches NPM, the container must share the host's network namespace. Without this, iptables rules created inside the container operate in an isolated network namespace and do not affect inbound traffic.
If you only want the dashboard (monitoring without active iptables blocking), you can switch to bridge mode:
# In docker-compose.yml — comment out network_mode and uncomment:
ports:
- "${DASHBOARD_PORT:-4000}:4000"
cap_add:
- NET_ADMIN
- NET_RAW
In bridge mode, bans will be created in the container's namespace but will not block host-level traffic.
Fail2Ban Configuration
Jails
The default jails installed by the container are:
| Jail | Filter | Purpose | Default: bantime / findtime / maxretry |
|---|---|---|---|
badbot |
badbot.conf |
Blocks known scanner/exploit UAs | 24h / 10m / 3 |
http-errors |
http-errors.conf |
Blocks high 4xx/5xx error rates | 1h / 5m / 15 |
npm-probe |
npm-probe.conf |
Blocks exploit path probes | 48h / 30m / 3 |
manual-bans |
(none) | Permanent manual bans via dashboard | permanent |
Customising jails
The Fail2Ban config is persisted in the f2b-config Docker volume. To edit it:
docker exec -it f2b-control-center bash
vi /etc/fail2ban/jail.local
fail2ban-client reload
Or mount a local directory instead of the volume for easier editing:
# In docker-compose.yml:
volumes:
- ./my-fail2ban-config:/etc/fail2ban
Log format
The default filters expect NPM access logs that include the real client IP in a [Client X.X.X.X] field at the end of each line. This format is produced when NPM is behind Cloudflare or another upstream proxy that forwards the real IP via X-Real-IP or X-Forwarded-For.
If your NPM logs have the client IP at the start of each line (standard nginx combined format), edit the failregex lines in each filter file:
# Example: switch http-errors filter to standard nginx format
docker exec -it f2b-control-center bash
vi /etc/fail2ban/filter.d/http-errors.conf
# Comment out Primary, uncomment Alternative lines
fail2ban-client reload
Test a filter against your actual logs:
docker exec f2b-control-center \
fail2ban-regex /nginx-logs/proxy-host-1_access.log \
/etc/fail2ban/filter.d/http-errors.conf
Optional Integrations
AbuseIPDB
Set ABUSEIPDB_API_KEY in .env. Free keys are available at abuseipdb.com.
Once configured:
- All ban cards show an abuse confidence score
- Use [THREAT] on any card to look up a specific IP
- Use [FORCE ABUSE] to background-check all currently banned IPs
- Use AUTO-BAN to scan logs and automatically ban IPs above the score threshold
Webhook
Set WEBHOOK_URL in .env to receive a POST request on every manual ban action:
{
"action": "ban",
"ip": "1.2.3.4",
"jail": "manual-bans",
"ts": "2026-02-20T14:00:00.000Z"
}
For Fail2Ban-triggered bans (from filters), use the webhook action in jail.local — see fail2ban/action.d/webhook.conf for setup instructions.
Cloudflare whitelist sync
Set CF_SYNC in .env to the path of a script inside the container. The script will be executed (fire-and-forget) whenever a whitelist change is made:
CF_SYNC=/usr/local/bin/cloudflare-whitelist-sync.sh
Mount the script into the container:
volumes:
- ./cloudflare-whitelist-sync.sh:/usr/local/bin/cloudflare-whitelist-sync.sh:ro
Managing the Container
# View logs
docker-compose logs -f
# Restart
docker-compose restart
# Update (rebuild image after pulling changes)
docker-compose pull
docker-compose build --no-cache
docker-compose up -d
# Reload fail2ban config without restart
docker exec f2b-control-center fail2ban-client reload
# View current ban status
docker exec f2b-control-center fail2ban-client status
# Manually ban an IP
docker exec f2b-control-center fail2ban-client set manual-bans banip 1.2.3.4
# Manually unban an IP
docker exec f2b-control-center fail2ban-client set manual-bans unbanip 1.2.3.4
Persistent Data
| Volume | Mount | Contents |
|---|---|---|
f2b-data |
/data |
ban-history.json — ban tracking database |
f2b-config |
/etc/fail2ban |
Jail config, filter files (survives image updates) |
| (bind mount) | /nginx-logs |
Your NPM log directory (read-only) |
To back up or inspect volumes:
docker run --rm -v f2b-data:/data busybox tar czf - /data > f2b-data-backup.tar.gz
Troubleshooting
Dashboard shows no bans / "Error: ..."
Fail2Ban may still be starting up. Check:
docker-compose logs f2b-control-center | grep fail2ban
docker exec f2b-control-center fail2ban-client ping
Bans are created but IPs are not actually blocked
Ensure network_mode: host is set in docker-compose.yml. In bridge mode, iptables rules are isolated to the container's network namespace and won't block host-level traffic.
Log scan returns no results
- Verify
NPM_LOG_DIRin.envpoints to the correct directory - Check the volume is mounted:
docker exec f2b-control-center ls /nginx-logs/ - Confirm log files match the pattern
proxy-host-*_access.log - Check that logs contain
[Client X.X.X.X]entries (see Log Format section)
"iptables: command not found" or permission errors
The container requires network_mode: host (or cap_add: [NET_ADMIN, NET_RAW] for bridge mode). Verify your docker-compose.yml configuration.
Fail2Ban filter not matching
Test the filter directly:
docker exec f2b-control-center \
fail2ban-regex /nginx-logs/proxy-host-1_access.log \
/etc/fail2ban/filter.d/http-errors.conf --print-all-matched
Project Structure
f2b-control-center/
├── Dockerfile # Single-container image (fail2ban + node + supervisor)
├── docker-compose.yml # Service definition
├── .env.example # Configuration template
├── entrypoint.sh # First-run init + supervisor start
├── healthcheck.sh # Docker health check
├── supervisor.conf # Process management (fail2ban + dashboard)
├── LICENSE
├── README.md
├── dashboard/
│ ├── package.json
│ ├── server.js # Express API + log scanning
│ └── public/
│ ├── index.html # Dashboard UI
│ ├── style.css # Terminal-style theme
│ └── favicon.svg
└── fail2ban/
├── jail.local # Jail configuration (installed on first run)
├── filter.d/
│ ├── badbot.conf # Bad bot UA detection
│ ├── http-errors.conf # HTTP error spamming
│ ├── npm-probe.conf # Exploit path probing
│ └── manual-bans.conf # Empty filter (manual bans only)
└── action.d/
└── webhook.conf # Optional: HTTP webhook on ban/unban
License
MIT — see LICENSE.