Enabling HTTP/3 in Nginx lets browsers move requests onto QUIC, which usually recovers faster than long-lived TCP sessions after packet loss, network handoffs, or other unstable last-mile conditions. A site that already serves HTTPS can add HTTP/3 on the same hostname and port instead of creating a separate endpoint for modern clients.

Current Nginx releases expose HTTP/3 through the ngx_http_v3_module and a quic listener on the same port as regular HTTPS. Browsers still reach the normal TLS listener first, learn that h3 is available from the Alt-Svc response header, and then reconnect over UDP when the path and client both support it.

The rollout only works when the running build includes --with-http_v3_module and the full network path allows both TCP and UDP on port 443. Current Nginx syntax also separates http2 on; from the listen directive, so older examples that use listen 443 ssl http2; should be updated before you add the QUIC sockets. Because clients cache Alt-Svc advertisements, start with a short ma value and clear the header during rollback with Alt-Svc: clear or ma=0.

Steps to enable HTTP/3 in Nginx:

  1. Confirm that the site already serves HTTPS on TCP 443.
    $ curl -I https://example.com/
    HTTP/2 200
    server: nginx/1.29.8
    content-type: text/html
    ##### snipped #####

    HTTP/3 discovery still starts on the regular HTTPS listener, so certificate or hostname problems must be fixed before QUIC negotiation can work.

  2. Verify that the running Nginx build includes the HTTP/3 module.
    $ nginx -V 2>&1 | grep -- --with-http_v3_module
    --with-http_v3_module

    If this check returns no match, the installed build cannot accept listen ... quic and the configuration test will fail until Nginx is replaced with a QUIC-capable package or build.

  3. Find the HTTPS server block that serves the target hostname.
    $ sudo nginx -T 2>/dev/null | grep -n "server_name example.com"
    /etc/nginx/conf.d/example.com.conf:3:    server_name example.com www.example.com;

    Use sudo nginx -T when possible because it shows the flattened running configuration instead of only one include path at a time.

  4. Update the HTTPS server block so it keeps the regular TLS listeners, adds QUIC listeners on the same port, and advertises h3 with Alt-Svc.
    server {
        listen 443 ssl;
        listen [::]:443 ssl;
        listen 443 quic reuseport;
        listen [::]:443 quic reuseport;
    
        http2 on;
    
        server_name example.com;
    
        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    
        add_header Alt-Svc 'h3=":443"; ma=60' always;
    
        ##### snipped #####
    }

    Current Nginx releases use http2 on; instead of the older listen 443 ssl http2; syntax. The HTTP/3 module defaults to http3 on;, so the essential enablement change is the listen ... quic sockets plus the Alt-Svc header.

    Use a short ma such as 60 during rollout. After the path is stable, raise it to a longer cache lifetime such as 86400. If a broader http block sets http3 off;, override it in this server block with http3 on;.

  5. Test the updated configuration before reloading the service.
    $ sudo nginx -t
    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful

    If the test fails with an unknown quic or http3 directive, the installed Nginx build or TLS library does not support the configuration yet.

  6. Reload Nginx to apply the configuration change.
    $ sudo systemctl reload nginx

    Reloading applies the new listeners without dropping active connections, but a failed reload can still leave the old configuration in place, so always keep the successful nginx -t result immediately before it.

  7. Confirm that Nginx is listening on UDP 443 for QUIC.
    $ sudo ss --udp --listening --numeric --processes 'sport = :443'
    State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process
    UNCONN 0      0      0.0.0.0:443      0.0.0.0:*     users:(("nginx",pid=1324,fd=6))
    UNCONN 0      0         [::]:443         [::]:*     users:(("nginx",pid=1324,fd=7))
  8. Allow inbound UDP 443 through the firewall path to the server.

    On UFW:

    $ sudo ufw allow 443/udp
    Rule added


    On firewalld:

    $ sudo firewall-cmd --permanent --add-port=443/udp
    success
    $ sudo firewall-cmd --reload
    success


    Cloud security groups and load balancers commonly require a separate rule for 443/udp.

  9. Verify that the Alt-Svc header is present on the normal HTTPS response.
    $ curl -I https://example.com/
    HTTP/2 200
    server: nginx/1.29.8
    alt-svc: h3=":443"; ma=60
    ##### snipped #####
  10. Verify actual HTTP/3 negotiation with an HTTP/3-capable client.
    $ curl --http3 -I https://example.com/
    HTTP/3 200
    server: nginx/1.29.8
    alt-svc: h3=":443"; ma=60
    ##### snipped #####

    Many current distro curl packages still omit HTTP/3 support. If --http3 or --http3-only is rejected as an unknown option, use a newer curl build or confirm the browser request in a network inspector and check that the negotiated protocol is h3.