quic_bpf with reuseport drops QUIC connections after reload

After every nginx -s reload, roughly half your HTTP/3 clients stall and fall back to TCP. Nothing is logged. This is the check that finds it.

HIGH quic_bpf_reuseport 6 min read Updated: August 2026

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:

  1. quic_bpf on; in the main context
  2. reuseport on a listen line that also carries quic
  3. worker_processes greater than 1 (including auto)
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