A fail2ban-style daemon for Linux that monitors web server access logs, detects malicious requests using PCRE2 regex patterns, and automatically bans offending IP addresses via the Firewalla MSP API by adding them to a managed target list backed by a firewall block rule.
- Monitors a web server log file in real time using Linux inotify
- Matches each new log line against configurable
failregexpatterns (fail2ban-compatible PCRE2 syntax) - When an IP's hit count reaches
maxretry, it is added to a Firewalla MSP target list via the REST API - The associated block rule on the Firewalla enforces the ban at the network perimeter — traffic from the banned IP is dropped before it reaches your server
- Runs periodic reconciliation to keep local state in sync with Firewalla
Banned IPs are persisted to a local banned.db file and survive daemon restarts. Log rotation is handled automatically.
- Linux (inotify required)
- Firewalla with MSP (Managed Security Portal) access
- MSP API version 2.10.0 or later (rule creation API)
- libcurl (for HTTPS API calls)
- libpcre2-8 (for PCRE2 regex matching)
apt install libcurl4-openssl-dev libpcre2-dev
make
Produces two binaries:
fwallascan2ban— the daemonfwallascan2ban-client— the CLI client
sudo make install
This installs:
/usr/local/sbin/fwallascan2ban/usr/local/bin/fwallascan2ban-client/etc/fwallascan2ban/fwallascan2ban.conf(from example, if not already present)/etc/fwallascan2ban/fwallascan2ban.env(from example, if not already present)/etc/systemd/system/fwallascan2ban.service
A single daemon instance can monitor up to 8 log files simultaneously using named [Log:name] sections in the config. Each source has its own file path, maxretry, and failregex patterns. At least one [Log:name] section is required.
[Log:tomcat]
log_pattern = /var/log/tomcat10/access_log.%Y-%m-%d.log
maxretry = 3
failregex = ^<HOST> - - \[.*\] "(GET|POST|HEAD) .*\.(php|env|git).*"
^<HOST> - - \[.*\] "CONNECT .*"
[Log:safeline]
log_pattern = /var/log/safeline-waf/attacks.log
maxretry = 1
failregex = "src_ip":\s*"<HOST>"[^}]*"action":\s*"deny"Bans carry a source-qualified tag: auto:tomcat, auto:safeline, etc.
SafeLine WAF blocks malicious HTTP requests on a separate VM. fwallascan2ban can automatically ban those IPs at the Firewalla perimeter by polling the SafeLine open platform API.
Note: The SafeLine free plan does not include syslog export. The polling approach works on all SafeLine plans including free.
A lightweight Python script (safeline-poll) is installed as a systemd timer that runs every 60 seconds. It queries the SafeLine REST API for new denied-IP events, writes each one as a JSON line to /var/log/safeline-waf/attacks.log, and records the highest event ID seen in a state file to avoid re-processing old events. fwallascan2ban monitors that log file as a [Log:safeline] source.
Log lines written to the file look like:
{"src_ip": "1.2.3.4", "deny_count": 1, "action": "deny", "id": 34}With maxretry = 1, every SafeLine block event triggers an immediate Firewalla ban.
Uncomment and fill in SAFELINE_API_TOKEN and SAFELINE_HOST in /etc/fwallascan2ban/fwallascan2ban.env. The API token is generated in the SafeLine console under System → Open Platform — you only see it once, so copy it immediately.
Then install the poller components:
sudo make install-safeline
This installs:
/usr/local/sbin/safeline-poll— the polling script/etc/systemd/system/safeline-poll.service— oneshot systemd service/etc/systemd/system/safeline-poll.timer— runs every 60 seconds
Add a [Log:safeline] section to /etc/fwallascan2ban/fwallascan2ban.conf:
[Log:safeline]
path = /var/log/safeline-waf/attacks.log
maxretry = 1
log_scan_interval = 0
failregex = "src_ip":\s*"<HOST>"[^}]*"action":\s*"deny"Enable and start the timer, then reload fwallascan2ban to pick up the new config section:
systemctl enable --now safeline-poll.timer
fwallascan2ban-client reload
To uninstall the SafeLine poller (leaves logs and state file in place):
sudo make uninstall-safeline
Edit /etc/fwallascan2ban/fwallascan2ban.env (chmod 600):
# Firewalla MSP (required)
FW_MSP_DOMAIN=yourname.firewalla.net
FW_MSP_TOKEN=your_personal_access_token
# SafeLine WAF poller (required only if using make install-safeline)
#SAFELINE_API_TOKEN=your_safeline_api_token_here
#SAFELINE_HOST=10.0.0.1
#SAFELINE_PORT=9443
Your MSP domain is the hostname you see in the browser when logged into the MSP portal. Your personal access token is generated under Account Settings in the portal.
The SafeLine variables are only needed if you run make install-safeline. See the SafeLine WAF Integration section for details.
Edit /etc/fwallascan2ban/fwallascan2ban.conf. The file is heavily commented — see fwallascan2ban.conf.example for full documentation of every option.
Key settings:
| Setting | Description | Default |
|---|---|---|
box_name |
Friendly name of your Firewalla box as shown in MSP | required |
target_list_name |
Name of the MSP target list to add banned IPs to | required |
max_targets |
Max IPs per target list before overflow list is created | 1000 |
reconcile_interval |
Seconds between periodic reconciliation passes | 3600 |
Per log-source settings (required; one [Log:name] section per source):
| Setting | Description | Default |
|---|---|---|
log_pattern |
Path to the log file, with strftime codes for rotation | required |
maxretry |
Number of pattern matches before an IP is banned | 3 |
log_scan_interval |
Seconds between directory scans for log rotation | 60 |
failregex |
One or more patterns to match; each must contain <HOST> |
required |
Patterns use standard PCRE2 syntax. The special token <HOST> is replaced at startup with a capture group matching IPv4 and IPv6 addresses. Syntax is compatible with fail2ban filter files.
Example patterns for Tomcat/web servers (place in [Log:tomcat]):
failregex = ^<HOST> - - \[.*\] "(GET|POST|HEAD) .*\.(php|env|git|cgi|sh|sql).*"
^<HOST> - - \[.*\] "(GET|POST|HEAD) .*/(wp-admin|wp-content|config|backup).*"
^<HOST> - - \[.*\] "CONNECT .*"
^<HOST> - - \[.*\] "-" 400 (?:-|\d+)
^<HOST> - - \[.*\] "GET .*/manager/html.*" 401 \d+Example pattern for SafeLine WAF (place in [Log:safeline]):
failregex = "src_ip":\s*"<HOST>"[^}]*"action":\s*"deny"Add your own IP, internal networks, and trusted addresses to ignoreregex in the [Filters] section to prevent accidental banning. The ignore list applies globally across all log sources. Supports single IPs and CIDR ranges. The placeholder IP and loopback addresses are always ignored automatically.
[Filters]
ignoreregex = 192.168.1.0/24
203.0.113.10systemctl enable --now fwallascan2ban
To rescan the current log from the beginning on startup (catches attacks that occurred before the daemon was running), add -r to ExecStart in the service file:
ExecStart=/usr/local/sbin/fwallascan2ban -c /etc/fwallascan2ban/fwallascan2ban.conf -rView logs:
journalctl -u fwallascan2ban -f
The fwallascan2ban-client tool communicates with the running daemon over a Unix socket.
| Command | Description |
|---|---|
fwallascan2ban-client status |
Daemon status, uptime, target list inventory, pending IPs |
fwallascan2ban-client banned |
All banned IPs with source tag and timestamp, grouped by list |
fwallascan2ban-client banned --fw-rules |
Same, plus a separate section for IPs blocked by Firewalla individual rules |
fwallascan2ban-client banned --sort-date |
All banned IPs sorted by date, oldest first (newest at bottom) |
fwallascan2ban-client banned --sort-date --fw-rules |
Sorted banned IPs, then a separate sorted section for Firewalla individual rule IPs |
fwallascan2ban-client pending |
IPs that have matched patterns but not yet reached maxretry |
fwallascan2ban-client rules |
Show active failregex scan patterns and maxretry threshold |
fwallascan2ban-client ban <ip> |
Manually ban an IP immediately |
fwallascan2ban-client unban <ip> |
Remove a banned IP from Firewalla and local db |
fwallascan2ban-client isbanned <ip> |
Check whether an IP is currently banned; shows source and timestamp if so |
fwallascan2ban-client testline '<log line>' |
Test a raw log line against all configured failregex patterns (read-only — does not affect hit counters); reports matched source, extracted IP, the specific pattern that matched, and the IP's current ban/hit status |
fwallascan2ban-client reload |
Reload config and trigger reconciliation (same as SIGHUP) |
fwallascan2ban-client rescan |
Switch to the newest log file |
fwallascan2ban-client rescan-all |
Reprocess the current log file from the beginning |
When a target list reaches max_targets, the daemon automatically creates a numbered overflow list (WebServer-Blocklist-2, -3, etc.) and a corresponding block rule for each. Reconciliation can consolidate lists when space becomes available.
Each entry in the banned list carries a source tag:
| Tag | Meaning |
|---|---|
auto:<name> |
Banned automatically by log pattern matching (e.g. auto:tomcat, auto:safeline) |
manual |
Banned manually via fwallascan2ban-client ban |
firewalla |
Found in Firewalla target list but not in local db (added externally via MSP portal or mobile app) |
fw-rule |
Blocked by a Firewalla individual IP block rule; removed from our target list to free capacity |
placeholder |
Placeholder IP keeping an otherwise empty list alive (never a real ban) |
Firewalla's threat intelligence may independently create individual IP block rules for the same addresses that fwallascan2ban manages via its target list. During each reconciliation pass, fwallascan2ban detects these individual rules and automatically removes the corresponding IPs from its managed target lists, freeing capacity for new bans. The affected IPs are recorded in the local database with the fw-rule source tag.
This means:
- Reconciliation actively deduplicates — if Firewalla adds an individual block rule for an IP that is in our target list, the next reconciliation removes it from the list. The IP remains blocked at the Firewalla level. No action is needed.
- Filter engine is seeded — after reconciliation, the log scanner marks all
fw-ruleIPs as already-banned so they are not re-processed. If the same IP appears in the log again, it is silently ignored. - Future reconciliations skip
fw-ruleIPs — they are not presented to the reconciliation logic as "expected in target list", so there is no re-add loop. - Unbanning —
fwallascan2ban-client unban <ip>removes the IP from the target list and local db, but any individual Firewalla block rule is not touched. The IP remains blocked at the Firewalla level until that rule is removed manually via the MSP portal. - If Firewalla removes the individual rule — the
fw-ruletag persists in the local db until the next log match or manualbancommand re-adds the IP through the normal path.
The local banned.db file includes a # db_version: 2 header (introduced in v1.3.0). On the first startup after upgrading from any pre-v1.3.0 installation, the daemon automatically detects the old format and copies the existing database to banned.db.v1.bak before continuing. The original data is preserved and the database is upgraded transparently.
| Path | Description |
|---|---|
/etc/fwallascan2ban/fwallascan2ban.conf |
Main configuration file |
/etc/fwallascan2ban/fwallascan2ban.env |
Credentials (MSP domain and token) |
/etc/fwallascan2ban/rsyslog-safeline.conf.example |
rsyslog config example (alternative: syslog forwarding from SafeLine) |
/var/lib/fwallascan2ban/banned.db |
Local persistent state of all banned IPs |
/var/lib/fwallascan2ban/banned.db.v1.bak |
Backup of pre-v1.3.0 database format (created once on first upgrade) |
/run/fwallascan2ban/fwallascan2ban.sock |
Unix socket for client communication |
/usr/local/sbin/safeline-poll |
SafeLine WAF event poller script (installed via make install-safeline) |
/var/log/safeline-waf/attacks.log |
SafeLine denied-IP events log (written by safeline-poll) |
/var/lib/fwallascan2ban/safeline-poll.state |
Highest event ID seen; prevents re-processing old events |