Nginx HTTP/3 and QUIC for WordPress: Setup and Performance Benefits

HTTP/3 (based on QUIC, RFC 9000) reduces page load time by eliminating TCP head-of-line blocking and reducing connection setup latency — connections over QUIC complete in 0-RTT on repeat visits compared to 1-RTT for TLS 1.3 over TCP. Nginx 1.25+ includes experimental QUIC/HTTP3 support that can be enabled alongside HTTP/2, giving browsers that support QUIC a faster path while falling back to HTTP/2 gracefully.

Problem: A WordPress server is still running HTTP/2 — HTTP/3 and QUIC offer lower latency on lossy connections and faster connection establishment, but enabling them in Nginx requires building with the QUIC patch or using a recent Nginx version.

Solution: Enable HTTP/3 in Nginx 1.25+ (mainline) — add listen 443 quic reuseport alongside the existing listen 443 ssl, set http3 on, and add the Alt-Svc response header to advertise QUIC support: add_header Alt-Svc 'h3=":443"; ma=86400'. Open UDP port 443 in the firewall. Verify with curl --http3 https://example.com.


The configuration below enables HTTP/3 on Nginx 1.25+ for a WordPress site, advertises QUIC support via the Alt-Svc header, tunes UDP buffer sizes for QUIC on Linux, and shows how to verify HTTP/3 is working with curl --http3.


# /etc/nginx/sites-available/helloadmin.com
# Requires: nginx compiled with --with-http_v3_module (nginx 1.25+)

server {
    listen 443 ssl;
    listen 443 quic reuseport;   # QUIC/HTTP3 on UDP 443
    listen [::]:443 ssl;
    listen [::]:443 quic reuseport;

    http2  on;          # HTTP/2 over TLS
    http3  on;          # HTTP/3 over QUIC

    server_name helloadmin.com www.helloadmin.com;

    ssl_certificate     /etc/letsencrypt/live/helloadmin.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/helloadmin.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;

    # Advertise HTTP/3 support to browsers
    add_header Alt-Svc 'h3=":443"; ma=86400';
    add_header X-Protocol $server_protocol;  # debug: shows h2 or h3

    # WordPress root
    root /var/www/html;
    index index.php;

    # QUIC 0-RTT: allow early data for GET requests only
    ssl_early_data on;
    add_header Early-Data $ssl_early_data;   # pass flag to PHP for anti-replay checks

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include        fastcgi_params;
        fastcgi_pass   unix:/run/php/php8.2-fpm.sock;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        # Pass Early-Data flag so WordPress can reject non-idempotent 0-RTT requests
        fastcgi_param  HTTP_EARLY_DATA $ssl_early_data if_not_empty;
    }

    # Static assets: long cache + immutable
    location ~* \.(js|css|png|jpg|webp|avif|woff2|svg)$ {
        expires    1y;
        add_header Cache-Control "public, immutable";
        add_header Alt-Svc 'h3=":443"; ma=86400';
    }
}


# Tune UDP buffer sizes for QUIC (add to /etc/sysctl.d/99-quic.conf)
net.core.rmem_max = 7500000
net.core.wmem_max = 7500000
sysctl -p /etc/sysctl.d/99-quic.conf

# Open UDP 443 in firewall
ufw allow 443/udp

# Verify HTTP/3 is working
curl --http3 -I https://helloadmin.com
# Look for: HTTP/3 200

# Check Alt-Svc header is being sent
curl -sI https://helloadmin.com | grep -i alt-svc
# alt-svc: h3=":443"; ma=86400


NOTE: QUIC 0-RTT (zero round-trip resumption) is vulnerable to replay attacks for non-idempotent requests — never process payment or state-changing actions from requests that carry the Early-Data: 1 header; in WordPress, check $_SERVER['HTTP_EARLY_DATA'] and return a 425 (Too Early) response for any AJAX action that modifies data when that header is present.