Skip to content

Powered by Grav

Config templates

Config templates

The web tier is built from two distinct template surfaces, and conflating them is the single most common operator mistake:

  1. The static template set under boa-private/aegir/conf/nginx/, copied verbatim to /etc/nginx/ and /var/aegir/config/ on install and every barracuda upgrade. Plain Nginx syntax, no render step.
  2. The Provision-rendered master http{} config, generated by server.tpl.php into /var/aegir/config/ when a platform or site is verified. This is where the log format, the rate-limit zones, the realip plumbing and every map/geo actually live — almost none of them are in the static set (the one exception is nginx_limit_req_zones.conf, in the table below).

If you go looking for BOA's rate limits or AI maps in aegir/conf/nginx/*.conf, you will find only the bgp_flood / boa_perhost_anon zone file — every other zone and every AI/abuse map lives in the rendered master config. Read the rendered master config, Edge policy and AI crawler policy instead.

The static template set

Files under aegir/conf/nginx/ and where each lands on disk:

File Lands at Purpose
nginx /etc/init.d/nginx sysvinit script for Nginx.
nginx.conf /etc/nginx/nginx.conf Top-level config: user/worker_processes, events tuning, gzip and keepalive defaults, the mime.types pointer and the conf.d/*.conf + sites-enabled/* include directives (no log directives — the log format and paths come from the rendered master config).
mime.types /etc/nginx/mime.types MIME type map.
fastcgi_params.txt /etc/nginx/fastcgi_params FastCGI parameter set, pulled into per-site vhosts via include fastcgi_params;.
nginx_compact_include.conf /var/aegir/config/includes/ Shared compact-vhost body — the $is_crawler drop, the high-load include and the FPM HTTP_HOST pin — included by the SQL-admin vhosts (nginx_sql_*.conf:34), not by the regular Drupal site vhosts.
nginx_wild_ssl.conf /var/aegir/config/server_master/nginx/pre.d/ Wildcard SSL listener — carries the single listen … quic reuseport.
nginx_limit_req_zones.conf /etc/nginx/conf.d/limit-req-zones-boa.conf The static set's one http-scope zone file: bgp_flood, boa_perhost_anon and its $boa_perhost_anon_key map — declared here rather than in the master render so per-instance vhost includes can rely on the declaration existing (lib/functions/nginx.sh.inc:808-813).
nginx_high_load_off.conf /data/conf/ Reduced-feature mode (returns 503) toggled by second.sh under high load — see Nginx debugging.
nginx_sql_adminer.conf server_master/nginx/vhost.d/ (rendered) Admin vhost for Adminer (DB UI).
nginx_sql_buddy.conf server_master/nginx/vhost.d/ (rendered) Admin vhost for sqlbuddy.
nginx_sql_cgp.conf server_master/nginx/vhost.d/ (rendered) Admin vhost for Collectd Graph Panel.
nginx_sql_chive.conf server_master/nginx/vhost.d/ (gated) Admin vhost for Chive — still deployed when the CHV extra is enabled or a legacy /var/www/chive exists (lib/functions/xtra.sh.inc:110-141); see Discontinued features.
nginx_speed_purge.conf (legacy — not deployed) Old Speed Booster cache-purge endpoint; any deployed copy is now deleted by _nginx_clean_legacy_config().
nginx-squeeze-init (legacy — unreferenced) Old init script left in the tree; no longer deployed or removed.

After deploy, service nginx reload (or a full restart, for init-script changes) picks them up.

Customisation rule

Do not edit any file under /etc/nginx/ or the deployed server_master/nginx/ set directly — they are overwritten on every barracuda upgrade. Operator changes go through the include mechanism in Custom rewrites & location blocks. The static set is read-only reference: open it to understand a default block your custom include must dovetail with, never to modify in place.

The Provision-rendered master http config

server.tpl.php emits the master http{} config when a platform/site is verified. Two surfaces operators routinely look for "in the templates" live only here.

Log format

The main log format leads with the realip-resolved client, so log analysis reports the real visitor and not the spoofable proxy chain:

NGINX
log_format main '"$remote_addr" $host [$time_local] '
                '"$request" $status $body_bytes_sent '
                '$request_length $bytes_sent "$http_referer" '
                '"$http_user_agent" $request_time "$gzip_ratio" '
                'proto="$server_protocol" '
                'alpn="$ssl_alpn_protocol" '
                'http2="$http2" '
                'xff="$proxy_add_x_forwarded_for" '
                'ut="$upstream_response_time"';

$remote_addr is the first token, so GoAccess (~h takes the first field) and scan_nginx both report the real client — on Cloudflare-fronted vhosts that is the realip-rewritten visitor IP, not the CF edge.

The full X-Forwarded-For chain is preserved as an xff="…" field near the end of the line, which GoAccess ignores along with proto=/alpn=/http2=. The actual final field is ut="$upstream_response_time"- whenever Nginx answered the request itself and a number only when a backend ran, so it is the one field that separates backend cost from wall-clock (quoted because it becomes a list when a request hits more than one upstream; trailing, so parsers that stop earlier are unaffected). The realip resolution this depends on is in Edge policy.

Rate-limiting zones

On modern Nginx ($nginx_is_modern) the master http config defines these global rate-limit / connection zones:

Zone Key Size / rate Purpose
limreq $boa_per_ip_limit_key 10m Per-IP connection limiting (limit_conn_zone). The key resolves to $binary_remote_addr but empties for 127.0.0.1, so requests proxied by BOA's local wild-ssl HTTPS front never share one bucket (map server.tpl.php:531-534).
search_limit $boa_per_ip_limit_key 10m, 3r/s Per-IP search rate cap (same loopback-exempt key).
search_flood $host 1m, 20r/s Per-vhost collective search ceiling across all source IPs — the primary defence against distributed one-request-per-IP search floods.
ai_search $ai_search_limit_key 10m, 1r/s AI search-bot soft limit.
ai_user $ai_user_limit_key 10m, 2r/s AI user-agent soft limit.
ai_utility $ai_utility_limit_key 5m, 1r/s AI utility-bot soft limit.
boa_i18n_anon $boa_i18n_anon_key 10m Per-vhost cap on in-flight anonymous localised (i18n) requests (limit_conn_zone, server.tpl.php:123).
bgp_flood $host 1m, 5r/s Per-vhost ceiling on D7 background_process self-request launches under /bgp-start/. Declared not in the master http config but in the BOA-written /etc/nginx/conf.d/limit-req-zones-boa.conf, so the per-instance vhost include can verify the declaration exists before rendering its consumer. See Request guards.
boa_perhost_anon $boa_perhost_anon_key 10m Per-vhost cap on in-flight anonymous page renders at location = /index.php (limit_conn, default 100, sheds 444; session cookie empties the key so editors are never counted). Declared in the same BOA-written limit-req-zones-boa.conf; tuned per instance via the provision option nginx_perhost_anon_conn. See Request guards.

The three ai_* zones key on empty-string maps that match only the relevant AI class, so no ordinary traffic is ever throttled by them. On pre-modern Nginx the fallback is a single limit_zone limreq $binary_remote_addr 10m;.

boa_i18n_anon keys on a value that is constant per vhost ($host) and non-empty only when three maps all agree:

  • $boa_i18n_guard — default 1 = on, per-host opt-out via the wildcard-included /data/conf/boa_i18n_guard.map, server.tpl.php:1074-1077.
  • $boa_i18n_path$request_uri with a leading 2-letter language prefix, optional script/region suffix like /pt-br/, or the D7 ?q=<lang>/ form, :1087-1091.
  • $boa_is_anon — reuses the $cache_uid session map, so logged-in users are never capped, :1095-1098; combining map :1102-1105.

It is enforced at location = /index.php (vhost_include.tpl.php:1824) via limit_conn boa_i18n_anon 24 — the cap is the per-instance provision option nginx_i18n_anon_conn, default 24 (vhost_include.tpl.php:1845-1850) — with limit_conn_status 444 (:1851). Detection, tuning and the opt-out procedure are owned by Request guards.

The four shared http-block map variables ($boa_i18n_guard, $boa_i18n_path, $boa_is_anon, $boa_i18n_anon_key) fall under the never-remove/rename rule for map variables a deployed vhost may use — see Edge policy.

limreq and search_limit shipped in BOA 5.9.3; search_flood and the ai_* zones were added in 5.10.1; the boa_i18n_anon limit_conn zone in 5.10.3. The AI zones and their per-vendor keys are detailed in AI crawler policy; the remaining maps and per-vhost limit_req / if guards in Edge policy.

HTTP/3, KTLS and the trusted-host fix

HTTP/3 (QUIC) and KTLS (kernel TLS offload) activate automatically on barracuda upgrade once the host's Nginx build supports them. The server-wide _NGINX_KTLS opt-out, where the QUIC reuseport listener lives, the HTTP/3 HTTP_HOST fix and how to pick HTTP/3 up after a host upgrade now have their own page: HTTP/3 and KTLS.

SQL-admin vhosts

The nginx_sql_*.conf admin vhosts (Adminer, sqlbuddy, CGP) are dedicated subdomain server blocks, gated by the xtras keywords and guarded by an IP-allowlist include rather than basic-auth or Ægir SSO. Their install gates and protection model are on SQL-admin vhosts.

Proxy vhosts speak HTTP/1.1 to origin

Since BOA 5.10.3 all five proxy templates set proxy_http_version 1.1; and clear the upstream Connection header (proxy_set_header Connection "";):

  • aegir/tools/system/conf/proxy.conf:16-17, 29-30
  • pln_proxy.conf:17-18
  • https_proxy_le.conf:23-24, 37-38
  • ssl_proxy.conf:85-86
  • the static set's nginx_wild_ssl.conf:57-58

In proxy.conf and https_proxy_le.conf the pair appears twice — once in the dedicated ACME-challenge location, once in location /.

Previously proxy_pass defaulted to HTTP/1.0 towards the origin, so origin access logs recorded proto=HTTP/1.0 for every proxied request regardless of the real client protocol — a false-positive trap for any IDS rule keyed on HTTP/1.0.

This covers both the migration/PX0 proxies and the local LE proxy fronting Ægir Hostmaster and Adminer over HTTPS (the z_<domain>_ssl_proxy.conf vhost that xoct ssl-gen generates from ssl_proxy.conf). It is also the safety precondition designed to keep the HTTP/1.0 registration-spam detector free of false positives behind BOA's own proxies — see scan_nginx scoring.

Already-deployed proxies self-update; no operator action is needed:

  • barracuda greps the deployed wildcard-SSL vhost for proxy_http_version and redeploys the template when the directive is absent (lib/functions/nginx.sh.inc:914-920).
  • octopus runs the same test against a deployed z_<domain>_ssl_proxy.conf, regenerating it via xoct ssl-gen (lib/functions/satellite.sh.inc:5967, condition at :5985-5991, regen at :5992-6002).

The change is purely additive — no upstream keepalive pool and no WebSocket Upgrade map was added.

Reading the templates

The static set is plain Nginx config — open and read it, no build step. It is useful when debugging unexpected vhost behaviour, or when writing a custom include that must dovetail with an existing BOA block. To understand the default rate-limiting/DoS rules, do not read the static set (it carries only the bgp_flood/boa_perhost_anon zone file); read the rendered master http config and Edge policy.

© 2026 BOA Documentation. All rights reserved.