Dockerized Fail2Ban + dashboard for Nginx Proxy Manager. - Single-container image (fail2ban + Node.js + supervisord) - Pre-built NPM filters: badbot, http-errors, npm-probe, manual-bans - Web dashboard with live ban feed, log scanner, AbuseIPDB integration - Configurable via environment variables and .env file - Persistent volumes for config and ban history - Webhook support for ban event notifications - README, .gitignore, MIT license Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
322 lines
9.7 KiB
Markdown
322 lines
9.7 KiB
Markdown
# F2B Control Center
|
||
|
||
A batteries-included Fail2Ban distribution for [Nginx Proxy Manager](https://nginxproxymanager.com/) 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**
|
||
|
||
```bash
|
||
git clone http://YOUR_GITEA_HOST/YOUR_USER/f2b-control-center.git
|
||
cd f2b-control-center
|
||
```
|
||
|
||
**2. Configure your environment**
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
Edit `.env` and set at minimum:
|
||
|
||
```env
|
||
# 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**
|
||
|
||
```bash
|
||
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:
|
||
|
||
```yaml
|
||
# 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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```yaml
|
||
# 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:
|
||
|
||
```bash
|
||
# 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:
|
||
|
||
```bash
|
||
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](https://www.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:
|
||
|
||
```json
|
||
{
|
||
"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:
|
||
|
||
```env
|
||
CF_SYNC=/usr/local/bin/cloudflare-whitelist-sync.sh
|
||
```
|
||
|
||
Mount the script into the container:
|
||
|
||
```yaml
|
||
volumes:
|
||
- ./cloudflare-whitelist-sync.sh:/usr/local/bin/cloudflare-whitelist-sync.sh:ro
|
||
```
|
||
|
||
---
|
||
|
||
## Managing the Container
|
||
|
||
```bash
|
||
# 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:
|
||
|
||
```bash
|
||
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:
|
||
```bash
|
||
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_DIR` in `.env` points 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:
|
||
```bash
|
||
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](LICENSE).
|