Skip to content

Powered by Grav

Abuse Guard configuration reference

Abuse Guard configuration reference

Almost 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). The one exception is the crawler-fleet family, whose defaults live in its own job, nginx_fleet.sh, and are read the same way.

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_GUARD404_*, _NGINX_I18N_FLOOD_*, _NGINX_FPM_*, _NGINX_FLEET_*) — they all take their 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 — except _NGINX_DOS_LOG, which is seed-only: an existing uncommented line is never overwritten by a retune, because it is the landing key of the converted .debug.monitor.log.cnf marker and a set value must survive fleet retunes. The other _NGINX_DOS_* keys are reset to their defaults by a retune (with _NGINX_DOS_LIMIT re-normalised to 399).

Override semantics

TXT
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 pass only: the first run, or when the saved offset is lost or the log has rotated. Every other pass reads just the bytes appended since the previous one (about 5 s), so raising this does not widen the detection window. It also sets how many recent lines an i18n-flood snapshot covers. 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 _NGINX_DOS_LOG=SILENT into /root/.barracuda.cnf once (its own internal default is held in _DEFAULT_NGINX_DOS_LOG), so a normally-managed box runs SILENT unless changed. Seed-only: a set value (including the NORMAL the .debug.monitor.log.cnf fold writes) survives retunes, unlike the other _NGINX_DOS_* keys.
_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

TXT
_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 towards 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.
TXT
_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.

Worked example — an expensive path of your own

The classic case is a calendar: a /events/<year>/<month>/<day> (or ?date=) space is a cheap page for a human and a bottomless one for a crawler that wants "every single date of every single year", each request an uncached render. Nothing here is a single knob — the layers below already act before you change anything, and the watch list is the one you add to:

  1. A crawler that says who it is is sorted by class at the edge. AI classes, scrapers and the $is_crawler set are answered 444 (or rate-limited per vendor) by the edge maps. A declared search crawler is served like a visitor, and $deny_on_high_load answers it 503 only once Spider Protection arms at high load, so a fleet that borrows a search engine's name renders pages until then.
  2. A fleet that lies about who it is is caught in aggregate: Detector 5 on the shared forged browser name, Detector 2 on volume — but only when its answers are mostly bad statuses. A date-space crawl answers 200 every time, so those two stay quiet, on purpose.
  3. The site stays up regardless because of the per-vhost cap on anonymous renders: it bounds how many uncached renders one site may hold in flight, so the front cache keeps absorbing the herd instead of collapsing under it. It ships at 100, which is inert behind a small pool — tune it per instance toward 1.5× the pool's pm.max_children (FPM capacity) before you expect it to bite.
  4. To have the participants banned, put the path on the watch list. In /root/.barracuda.cnf, restate the whole list plus your substring (the override replaces, it does not append):
TXT
_NGINX_PATH_FLOOD_WATCH="apachesolr_search|search_api_views_fulltext|search_api_fulltext|im_taxonomy_vid|/search/node|/search/user|/events/"

From the next scorer tick that prefix is declared under flood once 30 distinct addresses or 100 backend answers hit it inside the window, and each address that reached the backend at least 20 times (200 answers, not the 444s the maps already shed) is written to web.log and temp-banned for 15 minutes on ports 80/443 by the ban pipeline; repeat offenders escalate to a persistent deny. A render slower than 3 s counts extra. The thresholds are the search-flood defaults and sit above a busy public page's legitimate peak — lower them only on a real report, and remember that detection is post-hoc: a burst that finishes inside one 5-second window is served cheaply and banned for its next visit, never mid-burst.

Two things to keep straight: the substring is matched against the whole log line, so choose one that cannot occur in ordinary URLs (/events/ catches /events/2026/09/03 and ?q=events/… alike; a bare events would also match /news/events-recap); and the search engines you want indexing that calendar are exempt through the whitelist, so a legitimate crawl of the same space is neither declared nor banned.

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.
TXT
_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.

Guard-404 scraped-interactive-path (detector 6)

The no-referer request gates answer cold GETs to interactive-only paths (Flag toggles, HybridAuth windows) with a static 404 — each one a per-request bot verdict nginx already computed. This detector surfaces the campaign behind those gates once enough distinct IPs trip them at once, and can additionally ban the repeat-offender tail (cutting off the same address's parallel content-crawl 200s that no other detector counts). See the scoring engine for the mechanism, the crawler protections, and the rationale for excluding print paths.

The per-IP ban is opt-in and off by default. Unlike the HTTP/1.0 tell, this signal is browser-inducible: any third-party page can carry an <img referrerpolicy="no-referrer"> pointed at a hosted site's guarded path and make an innocent visitor's browser emit the exact trigger, so a default-on ban would let a stranger's page deny your visitors box-wide. Arm BAN per box on a confirmed campaign, the same way _NGINX_HARVEST_ACTION is armed.

Variable Default What it controls
_NGINX_GUARD404_DETECT YES Master switch; NO opts the box out.
_NGINX_GUARD404_ACTION REPORT REPORT logs GUARD404-WOULD-BAN lines and bans nothing; BAN arms the per-IP ban. Any other value falls back to REPORT, so a typo cannot silently arm it.
_NGINX_GUARD404_PATHS see below Bash ERE matched against the parsed, query-stripped request URI — the gate-family path class, case-insensitive like the nginx maps. Must be a valid ERE if overridden; a malformed override is detected at startup and reverted to the default with a warning.
_NGINX_GUARD404_WINDOW 600 Sliding-window length in seconds, persisted across runs in /var/xdrago/monitor/log/guard404.window.
_NGINX_GUARD404_IP_THRESHOLD 6 Windowed gate-404 count at which an IP is reported (or banned, in BAN mode). Sized above what a real visitor can produce — the gate's own 404 invites retries.
_NGINX_GUARD404_CAMPAIGN_IPS 40 Distinct windowed IPs at which one GUARD404-CAMPAIGN alert + forensic snapshot goes to i18n_flood.log. Alert-only, and it runs in both modes.
_NGINX_GUARD404_COOLDOWN 3600 Campaign alert cool-down in seconds.
TXT
_NGINX_GUARD404_PATHS="^/([A-Za-z]{2}(-[A-Za-z]+)?/)?([Ff][Ll][Aa][Gg]/(flag|unflag|FLAG|UNFLAG)/[A-Za-z0-9_]+/[0-9]|[Hh]ybrid[Aa]uth/window/[A-Za-z0-9_.-]+/?$)"

Non-positive-integer overrides of the numeric knobs revert to the defaults. Not autoupboa-seeded — script defaults apply unless added to /root/.barracuda.cnf by hand. There is deliberately no /24 aggregation knob: this botnet class rides ISP-dispersed CGNAT space where a /24 holds real users. clearwebbans clears this detector's window (and the HTTP/1.0 one) along with the ban logs, so an operator unban is not undone by carried-forward state.

Crawler-fleet fingerprint refusal

The one detector on this page that is not part of scan_nginx. The crawler-fleet refusal lives in its own per-minute job, /var/xdrago/nginx_fleet.sh, which declares a (vhost, user agent) fingerprint and renders the Nginx maps that answer its members 429 — see request guards for the enforcement and the ban pipeline for the generator. Its knobs are read from /root/.barracuda.cnf the same way, and every one of them is validated: a non-numeric value, or one outside the range below, is discarded with a CONFIG line in /var/xdrago/monitor/log/fleet.log and the shipped default is used, so a typo can never turn a gate into a hair trigger.

The refusal is ON by default, on every box, in BOTH scopes. There is no REPORT soak and no per-box arming step: _NGINX_FLEET_ACTION ships as BAN and _NGINX_FLEET_CRAWLER_ACTION inherits it. Action values are case-insensitive, and an invalid address action falls back to the shipped BAN with a CONFIG line rather than switching the protection off — a typo must never disarm it. An invalid crawler action inherits the (already validated) address action.

These knobs exist for reactive loosening after a confirmed legitimate report, not for arming. Loosen the one scope that produced the report, leave the other refusing.

Variable Default Range What it controls
_NGINX_FLEET_DETECT YES YES / NO Master switch. NO also releases every live refusal on the next run, because the store is then deleted rather than kept.
_NGINX_FLEET_ACTION BAN BAN / REPORT Address scope: browser-shaped agents and every agent that does not name itself a crawler. REPORT logs WOULD-BAN and neither stores nor renders that scope's fingerprints or members.
_NGINX_FLEET_CRAWLER_ACTION (empty) BAN / REPORT / empty Network scope: self-declared crawler names, refused per /16. Empty inherits _NGINX_FLEET_ACTION.
_NGINX_FLEET_WINDOW 300 180–900 Analysis window in seconds: how much of the recent log one pass considers.
_NGINX_FLEET_MIN_SPAN 180 60–900 Minimum age of the oldest line in the read tail before a pass may declare anything. A tail that spans less is skipped; the NOTE telling you to raise _NGINX_FLEET_TAIL_MB is written only when the tail was truncated, so a short log after a rotation is skipped silently. A value above _NGINX_FLEET_WINDOW reverts to 180.
_NGINX_FLEET_TAIL_MB 64 8–512 Megabytes read from the end of access.log each pass.
_NGINX_FLEET_IP_MIN 32 12–100000 Distinct non-allow-listed addresses a route class must be pulled from before it can be declared.
_NGINX_FLEET_REQ_MIN 48 24–1000000 Requests that route class must hold in the window.
_NGINX_FLEET_SHARE_PCT 8 2–100 Minimum percent of the vhost's windowed traffic the class must represent — a fleet that is noise against the site's real volume is left alone.
_NGINX_FLEET_NOREF_PCT 95 50–100 Minimum percent of the class that must carry no Referer. Also the per-address bar for joining the address scope.
_NGINX_FLEET_UNIQ_PCT 80 50–100 Minimum percent of requests hitting a distinct URL — a crawl, not a re-fetch.
_NGINX_FLEET_BAD_PCT 5 0–100 Maximum percent of the class that may be 5xx or 444. A cohort that is mostly already being refused is a different problem and is not declared.
_NGINX_FLEET_ALLOW_PCT 20 1–100 If allow-listed addresses reach this share of the class's distinct addresses, the pass logs REALIP-SUSPECT and declares nothing — the shape of a box whose realip trust chain is wrong.
_NGINX_FLEET_CAND_IPS 16 4–100000 Addresses a live fingerprint must still show in a later window before that pass records more members.
_NGINX_FLEET_NET_MIN_IPS 2 2–1000 Member addresses required inside one /16 before that network is refused.
_NGINX_FLEET_TTL 3600 600–86400 Seconds a declared fingerprint lives. Its expiry releases all of its members at once.
_NGINX_FLEET_MEMBER_TTL 21600 600–604800 Seconds a member address or network lives, while its fingerprint is live. This bounds the steering collateral.
_NGINX_FLEET_RELOAD_GAP 600 0–3600 Minimum seconds between reloads for a member-only change. A new or expired fingerprint reloads at once.
_NGINX_FLEET_MAX_FP 16 1–64 Live fingerprints kept per box; the newest-expiring win and the rest are dropped with a CAP line.
_NGINX_FLEET_MAX_ENTRIES 20000 100–100000 Keys per rendered map (addresses, networks); the newest win, with a CAP line.
_NGINX_FLEET_UA_EXEMPT (empty) ERE Additive. Whatever is set here is appended to the shipped exemption roster; it can never remove a shipped exemption. An override that makes the combined pattern invalid is dropped with a CONFIG line and the shipped roster alone applies.

The shipped roster exempts the search engines and their special-case fetchers, the social and messaging link previewers, the uptime monitors, and the AI classes that are allowed by default — the ones the AI crawler policy governs with its own per-vendor aggregate limits. Those agents are never declared as a fleet, whatever their volume; a declared class that matches the roster is logged EXEMPT and skipped.

Two more bounds are internal, not knobs: a user agent that cannot be carried as an exact map key (the printable-ASCII grammar, or longer than the key limit the master render's map_hash_bucket_size allows) is reported UNBANNABLE and never stored, so it cannot occupy a _NGINX_FLEET_MAX_FP slot a refusable fleet needs; and one pass records at most 2000 addresses per fingerprint.

Not autoupboa-seeded — the script's defaults apply unless the variable is added to /root/.barracuda.cnf by hand.

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 localised-request flood detector.
_NGINX_I18N_FLOOD_WINDOW 120 Sliding-window length in seconds, persisted across runs.
_NGINX_I18N_FLOOD_MIN_REQS 400 Windowed localised requests to one vhost before the volume+stress path can trip.
_NGINX_I18N_FLOOD_STRESS_PCT 15 Backend-stress gate: percent of windowed localised 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 localised 444s to one vhost that trip on their own, independent of volume. Attribution caveat: limit_conn_status is one-per-context, so these 444s come from either the Tier-A i18n guardrail or the general boa_perhost_anon render cap on the same vhost (see request guards) — a general flood can trip this early path too.
_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 six detectors and the Tier-B i18n-flood tally at once. The two passes that never ride the loop stay out of its reach: the Tier-B FPM-saturation trigger tails the PHP-FPM error logs, and the post-loop harvest pass reads its own bounded tail and exempts by UA (_NGINX_HARVEST_UA_EXEMPT), not by path.
  • _NGINX_DOS_IGNORE is tested inside _process_ip and only suppresses the per-IP counter for that line — every other detector still sees 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.
TXT
_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 — bar the _NGINX_FLEET_* family, read by nginx_fleet.sh — and both source /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.

© 2026 BOA Documentation. All rights reserved.