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.sh hard-codes a built-in default for each variable so the script is correct on a box that has never been touched. Separately, autoupboa seeds 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. autoupboa also normalises _NGINX_DOS_LIMIT back to 399 on 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.cnf yourself. 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.cnf and 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 +5 increment is sometimes mis-attributed to _NGINX_DOS_STOP. That is wrong on both counts: the +5 increments belong to the mode-1 /user, /node/add and /search patterns, while _NGINX_DOS_STOP adds 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_PATHS is 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_IGNORE is tested inside _process_ip and 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 (/graphql exempts /graphql/api/endpoint).
  • The match is against the real $request URI 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.cnf replaces 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