chore: clean up stale references and README

- jail.local: fix comment pointing to deleted docker-compose.cloudflare.yml
- .gitignore: remove .env entry (no env file in use)
- README: full rewrite to match current state (no .env, no webhook,
  correct file structure, inline compose config, CF setup instructions)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 15:38:20 +00:00
parent f87ce59842
commit 09124b10b9
3 changed files with 62 additions and 255 deletions

3
.gitignore vendored
View File

@@ -1,9 +1,6 @@
# Dependencies
dashboard/node_modules/
# Environment (contains secrets)
.env
# Runtime data
data/
*.json

303
README.md
View File

@@ -1,125 +1,63 @@
# F2B Control Center
A batteries-included Fail2Ban distribution for [Nginx Proxy Manager](https://nginxproxymanager.com/) environments, packaged as a single Docker container.
Fail2Ban + Nginx Proxy Manager in a single Docker container, with a web dashboard for monitoring and managing bans.
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
Pre-built filters detect bad bots, HTTP error spamming, and exploit probing against NPM logs. Bans are enforced via iptables (DOCKER-USER chain + INPUT), with optional Cloudflare WAF banning on top.
---
## Requirements
- Docker Engine 20.10+
- Docker Compose v2+
- Nginx Proxy Manager running and producing access logs
- Linux host with iptables support (for active banning)
- Docker + Docker Compose
- Linux host with iptables support
- `xt_string` kernel module on the host (`modprobe xt_string`)
---
## Quick Start
**1. Clone the repository**
```bash
git clone http://YOUR_GITEA_HOST/YOUR_USER/f2b-control-center.git
git clone https://git.thisisfake.lol/mykey/f2b-control-center
cd f2b-control-center
```
**2. Configure your environment**
Edit `docker-compose.yml` — at minimum review `SUBNETS_TO_IGNORE`. Then:
```bash
cp .env.example .env
docker compose up -d
```
Edit `.env` and set at minimum:
Dashboard at `http://YOUR_HOST:4000`
```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.
On first start the container installs the default Fail2Ban config into a persistent volume and begins monitoring NPM logs immediately.
---
## Configuration Reference
## Configuration
All settings are controlled via `.env` and the `docker-compose.yml` environment section.
All settings are in `docker-compose.yml`. Required fields are uncommented. Optional features are commented out — uncomment and fill in to enable.
| 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 (0100) |
| `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 |
| `PORT` | `4000` | Dashboard port |
| `SUBNETS_TO_IGNORE` | RFC1918 ranges | CIDRs excluded from scanning and banning |
| `ABUSEIPDB_API_KEY` | _(optional)_ | Enables threat scoring and auto-ban |
| `CF_EMAIL` | _(optional)_ | Cloudflare account email — enables WAF banning |
| `CF_APIKEY` | _(optional)_ | Cloudflare Global API Key |
---
## Network Mode
## Jails
The container runs with `network_mode: host` by default.
| Jail | Trigger | Ban time |
|---|---|---|
| `badbot` | Known scanner/exploit user-agents | 24h |
| `http-errors` | 15+ 4xx/5xx errors in 5 min | 1h |
| `npm-probe` | Exploit path probing (.env, wp-login, etc.) | 48h |
| `manual-bans` | Manual dashboard or CLI bans | Permanent |
| `recidive` | Repeat offenders (disabled by default) | 7d |
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:
To customise jails:
```bash
docker exec -it f2b-control-center bash
@@ -127,30 +65,37 @@ 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
```
## How Banning Works
### Log format
The container uses `network_mode: host` so iptables rules affect the host network stack. Two rules are inserted per ban:
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:
- **DOCKER-USER** — drops forwarded packets where the `X-Forwarded-For` header matches the banned IP (catches traffic routed through Cloudflare/CDN)
- **INPUT** — drops direct connections from the banned IP
Requires `xt_string` on the host:
```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
modprobe xt_string
# to persist across reboots:
echo xt_string >> /etc/modules
```
Test a filter against your actual logs:
---
## Cloudflare WAF
Uncomment and fill in `CF_EMAIL` and `CF_APIKEY` in `docker-compose.yml`. The container detects these on first start and installs the Cloudflare action alongside iptables — no other changes needed.
Get your Global API Key at: https://dash.cloudflare.com/profile/api-tokens
---
## Log Format
Filters expect NPM logs with the real client IP in a `[Client X.X.X.X]` field — the format NPM produces when behind Cloudflare or any proxy that forwards `X-Forwarded-For`.
Test a filter against your logs:
```bash
docker exec f2b-control-center \
fail2ban-regex /nginx-logs/proxy-host-1_access.log \
@@ -159,163 +104,35 @@ docker exec f2b-control-center \
---
## 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
## Useful Commands
```bash
# View logs
docker-compose logs -f
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
# Reload fail2ban config
docker exec f2b-control-center fail2ban-client reload
# View current ban status
# Check jail status
docker exec f2b-control-center fail2ban-client status
# Manually ban an IP
# Manual ban / unban
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
## Volumes
| Volume | Mount | Contents |
| Volume | Path | 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
```
| `f2b-data` | `/data` | Ban history database |
| `f2b-config` | `/etc/fail2ban` | Jail and filter config (survives image updates) |
| bind mount | `/nginx-logs` | NPM log directory (read-only) |
---
## License
MIT — see [LICENSE](LICENSE).
MIT

View File

@@ -1,15 +1,8 @@
# ── F2B Control Center — jail configuration (standard) ───────────────────────
#
# ── F2B Control Center — jail configuration ───────────────────────────────────
# Installed to /etc/fail2ban/jail.local on first container start.
# Persisted in the f2b-config Docker volume — survives image updates.
#
# WHITELIST: managed by the dashboard. The ignoreip line below is written by
# the entrypoint (from SUBNETS_TO_IGNORE) and updated live by the dashboard
# without restarting fail2ban.
#
# CLOUDFLARE: to also block IPs at the CF level, use docker-compose.cloudflare.yml
# instead of docker-compose.yml. It installs jail.cloudflare.local which
# adds the cloudflare action to every jail automatically.
# CLOUDFLARE: set CF_EMAIL + CF_APIKEY in docker-compose.yml to enable WAF banning.
# ─────────────────────────────────────────────────────────────────────────────
[DEFAULT]