HTTP/3 and KTLS — QUIC listeners, kernel-TLS opt-out, HTTP_HOST fix
HTTP/3 (QUIC) and KTLS (kernel TLS offload) are enabled in the BOA Nginx
config. They activate automatically on barracuda upgrade once the host's
Nginx build supports them; nothing is added on the configuration side. The
surfaces these directives render into — the static template set and the
Provision-rendered master http{} config — are on
Config templates.
KTLS (kernel TLS) moves TLS record encryption and decryption out of
OpenSSL in userspace and into the Linux kernel. BOA builds Nginx against an
OpenSSL that has kTLS compiled in and enables it with
ssl_conf_command Options KTLS; in every HTTPS server block — the directive
the KTLS verify command further down greps for.
Disabling KTLS server-wide (_NGINX_KTLS)
On OpenSSL 3.2 and later, KTLS can make the kernel receive path log
recv() failed (5: Input/output error) at [alert] and abort the connection
whenever a TLS control record — an alert, a handshake message, or a TLS 1.3
KeyUpdate — arrives without a control-message buffer. Most occurrences are
harmless: they fall on connection teardown and on bot traffic receiving small
error responses. The real cost is [alert]-level log noise that survives the
error_log crit default (so it can bury other alerts), plus a theoretical
truncation risk on a long-lived TLS 1.3 connection that rekeys mid-response
and a 2025 local privilege-escalation advisory (CVE-2025-39682) on the same
kernel code path.
Set _NGINX_KTLS=NO in /root/.barracuda.cnf and upgrade to turn KTLS
off across every HTTPS front. BOA writes the control file
/etc/nginx/no_ktls.conf; the Provision nginx service reads it — the same way
it reads basic_nginx.conf for config mode — and leaves nginx_has_ktls
false, so all four SSL vhost templates drop the directive, while the static
wildcard front and the per-domain z_<domain>_ssl_proxy.conf fronts strip it
on deploy. Re-enabling is just as clean: the wild-SSL refresh gate and the
per-domain ssl_proxy self-heal loop both check no_ktls.conf, so flipping
the value back to YES (which removes the file) settles across every front on
the next pass rather than the loops fighting the regeneration.
The default, _NGINX_KTLS=YES, keeps KTLS on and changes nothing — and it is
worth leaving on. When the kernel and NIC cooperate, KTLS raises static-file
throughput modestly, roughly 8-16% on Linux for large cached responses, though
on a typical Drupal/image workload the gain is small. Disable it only when the
[alert] log noise or the CVE-hardening posture outweighs that small win.
echo '_NGINX_KTLS=NO' >> /root/.barracuda.cnf
barracuda up-<tier> system # up-lts | up-pro | up-dev
# verify: empty output means KTLS is off on every front
nginx -T 2>/dev/null | grep 'Options.*KTLS'
# the control file mirrors the setting — present only when _NGINX_KTLS=NO
ls -l /etc/nginx/no_ktls.conf
# watch the alert rate before/after on a box that shows it
grep -c 'recv() failed (5: Input/output error)' /var/log/nginx/error.log
Listener placement — reuseport is wildcard-only
The reuseport flag belongs to the single wildcard SSL listener in
nginx_wild_ssl.conf (listen 127.0.0.1:443 quic reuseport;). Only one
listener per addr:port may carry reuseport, so the per-site rendered
SSL listeners use a plain listen *:<port> quic; without it
(vhost_ssl.tpl.php:66 and :163).
The listen IP is the wildcard *, not a
concrete site IP ($ssl_listen_ipv4 = "*", vhost_ssl.tpl.php:48) — that is
what lets any active IP on the host serve the vhost's cert. Do not expect — or
add — reuseport to a rendered per-site vhost; that is the wildcard
listener's job.
Picking up HTTP/3 after a host upgrade
When an upgrade brings HTTP/3 in, re-emit the vhosts so the new listener directives land per site. Verify every platform, then every site:
# As the Aegir user, per platform and per site:
drush @<alias> provision-verify
Trusted-host (HTTP_HOST) fix
Under HTTP/3 the request authority arrives in the :authority
pseudo-header, not a Host: header, so Nginx left Host empty and handed
Drupal an empty HTTP_HOST — breaking trusted-host checks, absolute-URL
generation and redirects on HTTP/3 connections.
BOA sets an explicit
fastcgi_param HTTP_HOST $host on the request paths that proxy to PHP-FPM:
- the standard vhost path (
vhost.tpl.php:63,118) - the SSL path (
vhost_ssl.tpl.php:122) - the ESI-microcache /
update.php/authorize.phpinternal handlers (vhost_include.tpl.php:1574,1903,1923)
The subdir handler already carried its own HTTP_HOST and is what this
change matched. $host carries the correct value under HTTP/1.1, HTTP/2 and
HTTP/3 alike, so Drupal always receives a populated HTTP_HOST. These
fastcgi_param lines are in the Provision-rendered per-site vhosts, not the
static set.
Cloudflare realip applied at install
cloudflare_realip.sh runs once during the barracuda upgrade chain — not
only from the daily cron — so Cloudflare-fronted vhosts resolve the real
visitor IP immediately after an upgrade, before the next cron tick. The
realip directives themselves are in the master http config; see
Edge policy.
Confirming HTTP/3 is active
curl --http3 -I https://yoursite.example.com/
Look for HTTP/3 200. In a browser, the protocol shows as h3 once the
client has switched — typically the second page load, after the Alt-Svc
header advertised HTTP/3.
Related
- Config templates — the static template set and the
master
http{}config these listener and TLS directives render into. - SSL operations — the SSL/cert workflow and
nginx_wild_ssl.conf, the wildcard listener that carriesreuseport. - Edge policy — the Cloudflare realip machinery the
install-time
cloudflare_realip.shrun feeds. - Nginx debugging — diagnosing vhost/TLS behaviour after an upgrade re-emits the listeners.