Configuration reference
Every tuning knob the Abuse Guard reads lives in scan_nginx.sh as a built-in
default; then /root/.barracuda.cnf is sourced and replaces any value it sets
(the override is a plain assignment, not a merge). This page is the authoritative
reference for those variables: the real default, what it controls, and how it
interacts with the rest of the scoring engine.
Where defaults come from. Two layers set these values.
scan_nginx.shhard-codes a built-in default for each variable so the script is correct on a box that has never been touched. Separately,autoupboaseeds a subset into/root/.barracuda.cnf(the_DEFAULT_NGINX_DOS_*block) so they appear as editable lines:_NGINX_DOS_LINES,_NGINX_DOS_LIMIT,_NGINX_DOS_MODE,_NGINX_DOS_DIV_INC_NR,_NGINX_DOS_INC_MIN,_NGINX_DOS_LOG,_NGINX_DOS_IGNORE,_NGINX_DOS_STOP.autoupboaalso normalises_NGINX_DOS_LIMITback to399on each pass. Those eight classics are the entire seeded set: the DDoS, path-flood, ignore-paths, 444-weight and php-probe knobs are not seeded, and neither is any knob of the newer detectors (_NGINX_HTTP10_AUTH_*,_NGINX_UA_BURST_*,_NGINX_I18N_FLOOD_*,_NGINX_FPM_*) — they all take the script's built-in defaults unless you add them to/root/.barracuda.cnfyourself. Where the seeded value differs from the script default, the seeded one wins (notably_NGINX_DOS_LOG).
Override semantics
built-in default assigned in scan_nginx.sh
↓
source /root/.barracuda.cnf
↓
any _NGINX_* line there REPLACES the default outright
- A scalar override (
_NGINX_DOS_LIMIT=199) simply wins. - A list override (
_NGINX_DOS_IGNORE_PATHS,_NGINX_PATH_FLOOD_WATCH) replaces the whole list — it is not appended to. Include everything the box needs; an empty value disables that feature. - A few weights (
_NGINX_DOS_444_WEIGHT,_NGINX_PHP_PROBE_WEIGHT) are not in the static default block. They are computed after the config is sourced —_NGINX_DOS_LIMIT / 3— but only if the operator did not set a numeric value. This deliberately preserves an explicit=0(disable) or custom value from/root/.barracuda.cnfand avoids a stale default when the config later lowers_NGINX_DOS_LIMIT.
Window and per-IP scoring
| Variable | Default | What it controls |
|---|---|---|
_NGINX_DOS_LINES |
1999 |
Lines of access.log read on a baseline run (the scan window). A distinct variable from _NGINX_DOS_LIMIT — not an alternate limit. |
_NGINX_DOS_LIMIT |
399 |
Per-IP score at which an IP is written to web.log. All weights below are derived from it. autoupboa also normalises this to 399 on each pass. |
_NGINX_DOS_MODE |
2 |
Per-IP algorithm. Mode 1 adds extra +5 increments for POST to /user, /user/register|pass|login, /node/add and GET to /node/add and /search; mode 2 (default) skips those. Both modes apply the _NGINX_DOS_STOP check below. |
_NGINX_DOS_LOG |
VERBOSE (script) / SILENT (seeded) |
Log verbosity: SILENT, NORMAL, or VERBOSE. The script's built-in default is VERBOSE, but autoupboa seeds _DEFAULT_NGINX_DOS_LOG=SILENT into /root/.barracuda.cnf, so a normally-managed box runs SILENT unless changed. |
_NGINX_DOS_DIV_INC_NR |
40 |
Divisor for the standard 4xx/5xx increment: _INC_NR = _NGINX_DOS_LIMIT / 40 (≈ 10 at the defaults). |
_NGINX_DOS_INC_MIN |
3 |
Floor for the computed increments — _INC_NR and _INC_S_NR are never less than this. |
The /user/login brute-force increment uses _INC_S_NR = _NGINX_DOS_LIMIT / 80
(≈ 5, floored at _NGINX_DOS_INC_MIN). The divisor _NGINX_DOS_DIV_INC_S_NR is
derived as _NGINX_DOS_DIV_INC_NR × 2 and is not a separately tunable knob.
| Variable | Default | What it controls |
|---|---|---|
_NGINX_DOS_444_WEIGHT |
_NGINX_DOS_LIMIT / 3 (≈ 133), computed |
Extra weight per confirmed 444 on a watched attack path (on top of _INC_NR). Honoured as set if numeric in config; 0 disables. |
_NGINX_PHP_PROBE_WEIGHT |
_NGINX_DOS_LIMIT / 3 (≈ 133), computed |
Extra weight for a .php request path that 404s on a Drupal docroot (webshell probe). Honoured as set if numeric in config; 0 disables. |
_NGINX_MIN_BLOCK_REQS |
3 |
Minimum raw (unweighted) requests an IP must make in the window before it can be individually blocked — stops one heavily-weighted hit from banning a one-request IP. Set to 1 to disable the floor. |
_NGINX_DOS_STOP — SQLi / blind-timing injection regex
_NGINX_DOS_STOP="WAITFOR.DELAY|DECLARE.*@x|/\*\*/|%27.*%29.*%3B|0x[0-9a-f]{6}"
This is not a generic flood-rate knob and not a "+5 counter" increment. It
is a regular-expression set of SQL-injection / blind-timing fingerprints
(WAITFOR DELAY, a DECLARE @x payload, inline /**/ comment obfuscation, the
%27…%29…%3B URL-encoded ') ; close, a long hex literal). When a log line
matches, _process_ip adds the full _NGINX_DOS_LIMIT to that IP's counter in
a single hit, saturating its score to the block threshold at once. A block still
also requires the IP to clear the _NGINX_MIN_BLOCK_REQS raw-request floor
(default 3), so a lone probe from an otherwise idle IP is caught in aggregate,
not instantly. The regex is matched (unanchored) against the whole log line, so an
injection attempt anywhere in the request trips it.
A
+5increment is sometimes mis-attributed to_NGINX_DOS_STOP. That is wrong on both counts: the+5increments belong to the mode-1/user,/node/addand/searchpatterns, while_NGINX_DOS_STOPadds the full_NGINX_DOS_LIMIT, not+5.
The default is also seeded into /root/.barracuda.cnf by autoupboa
(_DEFAULT_NGINX_DOS_STOP). An empty value disables injection-keyword scoring
entirely.
DDoS — shared-UA aggregate (detector 2)
See Detector 2 for the mechanism.
| Variable | Default | What it controls |
|---|---|---|
_NGINX_DDOS_UA_IP_THRESHOLD |
100 |
Distinct IPs sharing one User-Agent in the scan window before that UA is treated as an attack fingerprint. |
_NGINX_DDOS_UA_REQ_THRESHOLD |
1000 |
Total per-UA requests (across all IPs) that flags a UA even when its IP count is low but request volume is extreme — ~1000 in the ~5 s window is ~200 req/s of one exact UA string. |
_NGINX_DDOS_IP_MIN_REQS |
20 |
When a UA is flagged, only block contributing IPs that made at least this many requests with it. A complete human search session (results page, per-keystroke autocomplete, AJAX views, result clicks) stays clear of 20, while one IP hammering a shared UA does not. |
The defaults are sized for high-traffic boxes: within the ~5 s scan window the
single most common real mobile-browser UA string is shared by well over 20
distinct IPs, and a genuine distributed botnet randomises its UA per IP — one
UA shared by many IPs is the signature of a popular browser, not a bot. Tune
per box in /root/.barracuda.cnf; go tighter only on evidence of a genuinely
homogeneous burst. Abusive single IPs are still caught by the
per-IP scorer and the path-flood detector below.
Path-flood — search-amplification aggregate (detector 3)
See Detector 3 for the mechanism.
| Variable | Default | What it controls |
|---|---|---|
_NGINX_PATH_FLOOD_IP_THRESHOLD |
30 |
Distinct IPs on a watched path prefix before a flood is declared. Both 200 and 444 responses count toward this. Declaration alone never bans — the per-IP gate (_NGINX_PATH_FLOOD_IP_MIN_REQS) decides who is blocked — but a low value wastes work and widens the blast radius on a legitimate traffic peak. Tune upward further if a flood is still declared on legit peaks. |
_NGINX_PATH_FLOOD_REQ_THRESHOLD |
100 |
Total requests (200 + 444) to the prefix before a flood is declared — ~100 in the ~5 s window is ~20 req/s of search traffic site-wide, above legitimate interactive use, well below a real amplification flood. 444s come free from Nginx but still feed the aggregate so a one-request-per-IP distributed flood is caught. |
_NGINX_PATH_FLOOD_SLOW_SECS |
3 |
Upstream seconds above which a 200 counts as "slow" (real backend cycles consumed) and earns an extra per-IP increment. |
_NGINX_PATH_FLOOD_IP_MIN_REQS |
20 |
Per-(prefix, IP) 200-response count before that IP is listed during a flood. Kept deliberately modest at 20: under the default _NGINX_DOS_MODE=2 a backend 200 scores only +1 in the per-IP scorer, so a moderately heavy search scraper is caught primarily here — yet 20 still clears a shared CGNAT or Apple Private Relay egress aggregating many real users. Set to 1 to list every 200-sending participant. |
_NGINX_PATH_FLOOD_WATCH |
see below | Pipe-separated patterns matched against the full log line (path and query string) that mark a prefix as expensive/watched. |
_NGINX_PATH_FLOOD_WATCH="apachesolr_search|search_api_views_fulltext|search_api_fulltext|im_taxonomy_vid|/search/node|/search/user"
The defaults cover legacy Apache Solr search paths, the Search API Views and programmatic fulltext query parameters, the faceted-search taxonomy facet parameter, and Drupal core node/user search. Add site-specific expensive endpoint substrings per box; the override replaces the list.
HTTP/1.0 auth-spam (detector 4)
Catches the slow registration/credential-spam botnet that POSTs to Drupal auth paths over HTTP/1.0 with a forged modern-browser User-Agent — no browser built in the last ~15 years speaks HTTP/1.0 to a public HTTPS host, so the protocol itself is the tell. The bot paces roughly one request per IP every few minutes from a small CIDR block, staying under every volume detector above; this detector instead accumulates hits in a sliding window that persists across runs and bans through the standard pipeline. See the scoring engine for the mechanism.
| Variable | Default | What it controls |
|---|---|---|
_NGINX_HTTP10_AUTH_DETECT |
YES |
Master switch. NO opts the box out — required behind a non-BOA front proxy or CDN that downgrades to HTTP/1.0 at origin. BOA's own proxy layer sets proxy_http_version 1.1, so a correctly-updated BOA proxy tier does not downgrade; confirm via the access log that real clients show HTTP/1.1 / HTTP/2 before opting out. |
_NGINX_HTTP10_AUTH_PATHS |
see below | Bash ERE matched against the parsed, query-stripped request URI; the optional two-letter prefix mirrors i18n paths. Must be a valid ERE if overridden — a malformed override is detected at startup by an rc>1 [[ =~ ]] probe and reverted to the default with a warning, so a typo cannot silently disable the detector. |
_NGINX_HTTP10_AUTH_WINDOW |
600 |
Sliding-window length in seconds, persisted across runs in /var/xdrago/monitor/log/http10_auth.window. |
_NGINX_HTTP10_AUTH_IP_THRESHOLD |
3 |
Windowed per-IP auth-path hits at which the IP is banned. A lone stray HTTP/1.0 hit stays below it. |
_NGINX_HTTP10_AUTH_CIDR_THRESHOLD |
6 |
Windowed /24-aggregate hits at which every observed contributing IP of the /24 is banned — never an unseen address in the /24. Set very high to rely on the per-IP path only. |
_NGINX_HTTP10_AUTH_PATHS="^/([a-z]{2}/)?user/(register|password)(/|$)"
Non-positive-integer overrides of the three numeric knobs revert to the
defaults. None of these is autoupboa-seeded — they take the script defaults
unless added to /root/.barracuda.cnf by hand.
UA-burst scanner fleet (detector 5)
The per-IP scorer and detector 2 both exclude 301 redirects and both need
high per-IP or per-UA volume, so a distributed scanner fleet — dozens of cloud
IPs sharing one forged UA, a few requests each, mostly redirects and 404s to
non-Drupal CMS paths — slips through every gate above. This detector groups by
UA, counts all statuses including 301, and trips only when a UA is shared
by many IPs and its traffic is overwhelmingly "bad".
| Variable | Default | What it controls |
|---|---|---|
_NGINX_UA_BURST_DETECT |
YES |
Master switch; NO opts the box out. |
_NGINX_UA_BURST_IP_MIN |
12 |
Minimum distinct IPs sharing one exact UA in the scan window. |
_NGINX_UA_BURST_REQ_MIN |
60 |
Minimum total requests for that UA across all its IPs in the window. |
_NGINX_UA_BURST_BAD_PCT |
80 |
Minimum percent of the UA's requests that are "bad" — 3xx redirects (301/302/307/308) or client errors (400/403/404/410) — before the fleet is declared hostile. This is the false-positive keystone: a legitimate browser fleet is mostly 200/304 and stays far below it. |
_NGINX_UA_BURST_IP_MIN_BAD |
3 |
Per-IP bad-probe floor once the fleet trips — a legitimate visitor sharing the UA sent 200s, zero bad probes, and is never blocked. |
Non-positive-integer overrides revert to the defaults (the bad-ratio division
requires _NGINX_UA_BURST_REQ_MIN of at least 1). Not autoupboa-seeded. Tune
tighter, never looser, on real reports.
Tier B — i18n flood + FPM saturation (alert-only)
These two paths alert and snapshot only — no per-IP bans. A translation
flood spreads across thousands of IPs at one or two requests each, so per-IP
banning is futile; the value is the early alert plus a forensic snapshot of top
talkers, UAs and path classes. Alerts land in
/var/xdrago/monitor/log/i18n_flood.log; snapshots, the cross-run
window.state and the fpm_maxchildren.pos byte-offset file live under
/var/xdrago/monitor/log/i18n_flood/.
| Variable | Default | What it controls |
|---|---|---|
_NGINX_I18N_FLOOD_DETECT |
YES |
Master switch for the localized-request flood detector. |
_NGINX_I18N_FLOOD_WINDOW |
120 |
Sliding-window length in seconds, persisted across runs. |
_NGINX_I18N_FLOOD_MIN_REQS |
400 |
Windowed localized requests to one vhost before the volume+stress path can trip. |
_NGINX_I18N_FLOOD_STRESS_PCT |
15 |
Backend-stress gate: percent of windowed localized requests that are slow, 5xx or 444 before the volume+stress path trips. |
_NGINX_I18N_FLOOD_SLOW_SECS |
3 |
Slow threshold (whole seconds) for the stress gate. |
_NGINX_I18N_FLOOD_C444_THRESHOLD |
40 |
Early trip: windowed localized 444s to one vhost — the Tier-A guardrail actively shedding the flood (see request guards) — that trip on their own, independent of volume. |
_NGINX_I18N_FLOOD_COOLDOWN |
300 |
Per-vhost alert cool-down seconds, so a multi-minute burst yields a handful of records, not one per scan cycle. |
_NGINX_FPM_SAT_DETECT |
YES |
Master switch for the PHP-FPM saturation trigger. |
_NGINX_FPM_ERR_GLOB |
/var/log/php/php*-fpm-error.log |
Glob of PHP-FPM per-version error logs the trigger reads — byte-offset tracked per file, so only new ceiling hits since the last run are acted on. |
_NGINX_FPM_SAT_PATTERN |
reached max_children setting |
The literal the FPM master logs when a pool hits its pm.max_children ceiling. |
Exemptions
These two knobs exempt at different scopes:
_NGINX_DOS_IGNORE_PATHSis tested at loop scope before any detector runs (_is_ignored_request … && continue), so it exempts the line from all three detectors at once._NGINX_DOS_IGNOREis tested inside_process_ipand only suppresses the per-IP counter for that line — the shared-UA and path-flood aggregates still see it.
The path-exemption mechanism (the laundering-proof URI parse) is documented in detail in The exemption gate; these are the configuration rows.
| Variable | Default | What it controls |
|---|---|---|
_NGINX_DOS_IGNORE |
doccomment |
Keyword(s) that, when found on a 200/302 line, exempt it from per-IP scoring (a regex fragment, matched against the line). |
_NGINX_DOS_IGNORE_PATHS |
see below | Space-separated URI prefixes exempt from all IDS scoring. |
_NGINX_DOS_IGNORE_PATHS="/shopify/webhook /quickbooks/webhook /stripe/webhook /paypal/webhook /github/webhook /gitlab/webhook /graphql /public-api /oauth2"
_NGINX_DOS_IGNORE_PATHS rules:
- Paths are space-separated, each a leading-slash absolute path with no
trailing slash (
/shopify/webhook, not/shopify/webhook/). - Each entry matches that exact path or any sub-path under it (
/graphqlexempts/graphql/api/endpoint). - The match is against the real
$requestURI only — query stripped, any..or%-escape refused — so a token in a User-Agent, Referer, or query string can never launder an exemption (see page 01 for why). - The override in
/root/.barracuda.cnfreplaces the default list, so include every endpoint the box needs. An empty value disables the feature.
These exist because machine/API endpoints authenticate per request at the
application layer (HMAC, OAuth), not by IP — counting them per-IP self-bans a
legitimate webhook provider or API client. The defaults are shipped in the script
(not only the per-box override) so the exemption survives a /root/.barracuda.cnf
regeneration.
Where these are set
All of the above are read by scan_nginx.sh, which sources /root/.barracuda.cnf,
so any of them can be overridden there. The consolidated _NGINX_* knob list with
types and source references is in the
Variables reference; the control-file format and
the broader DoS-protection family sit in the
control files & INI topic. Once an IP is scored over
_NGINX_DOS_LIMIT, enforcement proceeds through
the ban pipeline and the
request guards.
Related
- scan_nginx scoring engine — the detector logic these knobs tune.
- The ban pipeline — the
.instant.csf.block/.full.csf.cleanupmarkers and the escalation thresholds. - Operations + tuning — applying these overrides in practice and enabling debug output.
- Variables reference — the consolidated
_NGINX_*table.