yum upgrades for production use, this is the repository for you.
Active subscription is required.
Technical Briefing: NGINX HTTP/3 reuseport, quic_bpf, and SELinux
The Problem: HTTP/3 Silently Fails Without reuseport
With worker_processes greater than 1, a QUIC listener configured without reuseport shares a single UDP socket across all workers. Because QUIC has no accept() step, every datagram arrives on that one socket, and whichever worker wakes first grabs it — even if it does not own that connection. The owning worker then receives mid-connection datagrams it cannot handle, producing H3_REQUEST_CANCELLED, stateless resets, and idle timeouts.
The failure mode is silent: clients fall back to HTTP/2, so users get slower connections rather than errors, and nothing appears in the NGINX error log at any level.
Production Impact
Over 24 hours on real infrastructure, 28–40% of HTTP/3 checks fell back to HTTP/2 in bursts. Failure counters included:
- 275
H3_REQUEST_CANCELLED (local) - 61 stateless resets
- 33 “no recent network activity” timeouts
- 28 context deadline exceeded
On a clean Rocky Linux 10 VM with 4 workers and no reuseport, 13 of 40 curl --http3-only requests failed — a 32.5% failure rate.
The Fix: reuseport on Exactly One Listen Directive
Add reuseport to exactly one listen ... quic directive per listen address. Every other virtual host sharing that address must use plain listen 443 quic; with no options.
Listen options belong to the address, not the virtual host, and duplicating them is a config error:
duplicate listen options for 0.0.0.0:443
This is a correctness requirement, not a performance optimization.
Restart, Don’t Reload
After nginx -s reload, the old non-reuseport socket stays open, inherited by the new worker generation, and keeps stealing datagrams. The correct sequence is:
nginx -tsystemctl restart nginx- Verify
Diagnosis
ss -ulnp 'sport = :443'
- One
UNCONNline shared by all workers = broken. - One line per worker = correct.
With N workers, a single shared socket means roughly (N-1)/N of mid-connection datagrams land on the wrong worker.
Verification Caveats
- Check the HTTP/2 fallback’s
Alt-Svcheader. If it still advertisesh3, the server offers HTTP/3 and the client simply could not complete QUIC. Alerting on “protocol downgrade” alone produces false alarms. - Test from outside the network with
curl --http3-only -sv. UDP 443 is a separate firewall rule, and local probes never exercise it. - On RHEL-family systems,
firewall-cmd --list-servicesmust includehttp3or an explicit UDP 443 rule.
Packaging
Stock RHEL-family nginx packages ship without HTTP/3. The GetPageSpeed NGINX Extras repository provides NGINX with HTTP/3 enabled plus the SELinux policy for quic_bpf.
The quic_bpf SELinux Trap
Enabling quic_bpf on; — which preserves QUIC connections across binary upgrades and enables connection migration — kills NGINX at startup on SELinux-enforcing RHEL-family systems with:
failed to create BPF map (13: Permission denied)
The “check limits” message misdirects toward RLIMIT_MEMLOCK; errno 13 is EACCES, an LSM denial signature (capability/memlock failures return EPERM). NGINX runs as httpd_t, which the distribution policy grants nothing on the bpf class, and the denial is dontaudit-suppressed, so ausearch -m avc comes back empty.
SELinux Fix
NGINX 1.30.4-68 and later (and nginx-mod 1.30.4-66 and later) include the nginx-gps SELinux module with the required bpf, capability2, and net_admin permissions behind a default-off boolean:
setsebool -P nginx_quic_bpf 1
systemctl restart nginx
The BPF map is created by the master at startup. Verify with bpftool map list showing a sockhash map.
Older-Package Caveat
Packages before nginx 1.30.5-69 / nginx-mod 1.30.5-67 shipped the policy as a binary .pp compiled by a newer SELinux toolchain; systems with even one update level older libsepol silently failed to load it. Current packages ship version-agnostic CIL.
If getsebool nginx_quic_bpf reports the boolean doesn’t exist, update the package; otherwise update the SELinux userland and load manually with semodule -i.
Related Known Issues
- With
quic_bpf on, every reload silently kills new QUIC handshakes on stale sockets — an upstream bug F5 has not merged a fix for, patched in these packages. - A separate
$http_hostempty-on-HTTP/3 request-variable bug was also patched ahead of upstream.
Tooling
Gixy has a dedicated quic-bpf-reuseport check that catches the related reload-time hazard in CI. GetPageSpeed Amplify runs scheduled gixy scans across hosts and ties findings to live NGINX runtime metrics, drop-in compatible with the deprecated nginx-amplify-agent (EOL January 2026).
Read the full article: NGINX QUIC reuseport: HTTP/3 Silently Breaks Without It
