Skip to content

Powered by Grav

HTTP/3 and KTLS — QUIC listeners, kernel-TLS opt-out, HTTP_HOST fix

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.

BASH
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:

SH
# 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.php internal 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

SH
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.

  • 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 carries reuseport.
  • Edge policy — the Cloudflare realip machinery the install-time cloudflare_realip.sh run feeds.
  • Nginx debugging — diagnosing vhost/TLS behaviour after an upgrade re-emits the listeners.

© 2026 BOA Documentation. All rights reserved.