What Gixy reports
[quic_bpf_reuseport] quic_bpf with reuseport silently drops QUIC connections after reload
Severity: HIGH
Reason: quic_bpf on + reuseport on a QUIC listener with multiple
worker processes drops ~50% of QUIC connections after reload.
The three ingredients
This check only fires when all three of these are true at once. Any one of them alone is fine, which is exactly why the bug is so hard to spot by reading the config:
quic_bpf on;in the main contextreuseporton alistenline that also carriesquicworker_processesgreater than1(includingauto)
quic_bpf on;
worker_processes auto;
http {
server {
listen 443 quic reuseport;
listen 443 ssl;
server_name example.com;
}
}
Why it breaks
quic_bpf exists to solve a real problem. QUIC connections survive an IP or port
change by their connection ID, not by their 4-tuple, so a migrated packet can land on the
wrong worker. nginx attaches an eBPF program to the reuseport socket group that
reads the connection ID and routes each packet to the worker that owns it.
On reload, nginx starts a fresh set of workers and retires the old ones. The BPF socket map, however, still holds entries pointing at workers that are shutting down. Packets for live connections keep being steered at sockets nobody is servicing any more, and those connections die. Clients see a timeout and quietly fall back to HTTP/2 over TCP.
Nothing is logged
There is no error, no warning, and no counter for this. Your error log stays clean and your HTTP/3 traffic share just sags after each reload until clients reconnect. That is why a static check is the practical way to catch it.
This is a known upstream issue (nginx/nginx#425), unfixed in mainline at the time of writing.
The fix
Turn quic_bpf off. You lose optimal connection-migration routing; you stop
losing connections on every reload. For nearly every deployment that is the right trade.
quic_bpf off;
worker_processes auto;
http {
server {
listen 443 quic reuseport;
listen 443 ssl;
server_name example.com;
}
}
The other two ways out, if you need quic_bpf:
-
Drop
reuseport. Without it there is no socket group for the BPF program to attach to, so the stale-map problem cannot occur. You give up the kernel-side load balancing across workers. - Run a single worker. Correct, and almost never acceptable in production.
Verify the fix
# Confirm the config no longer trips the check
gixy /etc/nginx/nginx.conf
# Watch HTTP/3 survive a reload: start a long-lived QUIC request,
# reload in another shell, and confirm it completes.
curl --http3-only -sS -o /dev/null -w '%{http_version} %{response_code}\n' \
https://example.com/large-file
# In another shell, mid-transfer:
nginx -s reload
Repeat it a handful of times. Before the fix you will see intermittent stalls and
2 in the http_version field as clients give up on QUIC; after it,
a consistent 3.
When this is a false positive
It is not, in the strict sense. If all three conditions hold, the bug is present. The only case where you might knowingly leave it is a server that is never reloaded without a full restart, for example one whose config only changes on redeploy of an immutable image. If you reload to pick up renewed certificates -- and with 90-day certificates almost everyone does -- you are exposed.
Reference
- quic_bpf_reuseport in the Gixy documentation — what the check inspects and its options
- All Gixy checks — the full list by severity
- Plugin source — the exact detection logic