Configuration
The daemon is configured entirely through environment variables and two on-disk files. No code change is required to tune behaviour.
Environment variables
| Variable | Type | Default | Purpose |
|---|---|---|---|
ABUSIVE_FAMILY | v4 / v6 / any | any | Restrict matching to one IP family. |
ABUSIVE_MIN_HITS | integer ≥ 1 | 3 | Hits from one source before the IP is written to the blocklist. |
ABUSIVE_SCOPE | comma-list | request,ua | Which log fields to evaluate. Valid: request, ua, referrer. |
ABUSIVE_REFERRER_DOMAINS | whitespace/comma list | (empty) | Allowed referrer hosts. Only used if referrer is in ABUSIVE_SCOPE. |
ABUSIVE_SYSLOG_TAG | string | abusive_http_watch | Syslog program tag. |
ABUSIVE_SYSLOG_FACILITY | string | daemon | Syslog facility. Use local0–local7 for custom routing. |
ABUSIVE_SYSLOG_PRIO | info / notice / warn / err | notice | Syslog priority for added events. |
ABUSIVE_DEBUG | 0 / 1 | 0 | Verbose stderr trace of every line. Useful only during scoping. |
Notes
ABUSIVE_MIN_HITS— Setting this to1blocks on the first hit. That is more aggressive but increases the false-positive risk from one-shot probes that happen to traverse a benign cloud IP later. The default of3is a good compromise.ABUSIVE_SCOPE—referreris off by default because a malicious referrer can put your visitor’s IP into the blocklist via a drive-by. Only enablereferrerif you also setABUSIVE_REFERRER_DOMAINSto a strict allowlist.ABUSIVE_FAMILY— Useful when you want to mirror the daemon across two processes: one for IPv4 and one for IPv6, each writing to its own blocklist file.
Token set
The detection regex set is hardcoded in the script (@TOKENS) and
covers eight attack classes — PHP exploitation, WordPress probes,
path traversal, sensitive paths, RCE/CGI attempts, SQL injection,
login-loop probes, and null-byte injection.
To tune the set:
- Open the script.
- Adjust the
@TOKENSarray — add patterns, remove ones that fire false positives in your environment. - Restart the daemon.
The token set is deliberately conservative. Adding tokens like
/admin, /login, or phpmyadmin without scoping will cause false
positives on legitimate traffic.
Whitelist file
Path: /etc/abusive_http_whitelist
One entry per line. Three formats are supported:
# Comments allowed
203.0.113.42 # single IPv4
2001:db8::abcd # single IPv6 (full or shortened)
203.0.113.0/24 # IPv4 CIDR
2001:db8::/32 # IPv6 CIDRWhitelisted IPs are skipped completely — they never increment hit counters and never end up in the blocklist, even if their requests match every token in the set.
Use the whitelist for:
- Office and VPN public IPs (operators triggering scanners by accident).
- Search-engine crawler IP ranges that are documented to be legitimate (Google, Bing). Test before adding — the whitelist is rarely needed for crawlers because their requests do not match attack tokens.
- Monitoring services that probe
/wp-login.phpdeliberately.
Blocklist file
Path: /var/www/run/abusive_http_hosts
Format: one IP per line, no comments, no headers, no duplicates.
The daemon enforces de-duplication via an in-memory hash and
flock(LOCK_EX) on writes.
The file is append-only at runtime — old entries are never removed by the daemon. To prune (e.g. expire entries after 30 days), use external rotation:
# In a daily cron
mv /var/www/run/abusive_http_hosts /var/www/run/abusive_http_hosts.old
touch /var/www/run/abusive_http_hosts
chown _www:_www /var/www/run/abusive_http_hosts
doas rcctl restart abusive_http_watchThis forces the daemon to rebuild its in-memory state from an empty file. The firewall reloads the new (empty) blocklist on its next cron tick.
Firewall consumption
The blocklist file format is intentionally flat so that any layer can consume it. Common integrations:
OpenBSD pf
table <abusive> persist file "/var/www/run/abusive_http_hosts"
block in quick on egress from <abusive>Reload table:
pfctl -t abusive -T replace -f /var/www/run/abusive_http_hostsLinux nftables
table inet filter {
set abusive_v4 {
type ipv4_addr
elements = { ... }
}
chain input {
ip saddr @abusive_v4 drop
}
}Reload set from file via a small helper:
nft 'flush set inet filter abusive_v4'
awk '/^[0-9]/ {print}' /var/www/run/abusive_http_hosts | \
xargs -I{} nft 'add element inet filter abusive_v4 { {} }'Apache
<RequireAll>
Require all granted
Require not ip 203.0.113.0/24
# … one Require not ip per blocklist entry
</RequireAll>Generated by a templater (m4, sed) from the blocklist file on a
cron. Slower to react than firewall integration, but works when you
do not own the firewall layer.
nginx
geo $abusive {
default 0;
include /var/www/run/abusive_http_nginx.conf;
}
server {
if ($abusive) { return 444; }
}Where abusive_http_nginx.conf is generated by a template from the
blocklist file.