HTTP/3 is not a new protocol layered on top of TCP, but rather a completely new protocol that runs over UDP.
Let’s get Nginx serving HTTP/3. The standard Nginx distribution doesn’t include QUIC support, so we’ll need to build Nginx from source with a specific patch.
First, we need to get the necessary prerequisites. On Ubuntu/Debian, this means:
sudo apt update
sudo apt install build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev openssl
Next, we’ll download the Nginx source code. It’s crucial to pick a stable Nginx version. Let’s go with 1.25.3 as of this writing. You’ll also need the nginx-quic patch, which is maintained by the Cloudflare team.
wget https://nginx.org/download/nginx-1.25.3.tar.gz
wget https://github.com/cloudflare/nginx-quic/archive/refs/tags/0.4.0.tar.gz
tar -xzf nginx-1.25.3.tar.gz
tar -xzf 0.4.0.tar.gz
mv nginx-quic-0.4.0 nginx-quic
Now, we’ll prepare for the build. We need to apply the nginx-quic patch to the Nginx source.
cd nginx-1.25.3
patch -p1 < ../nginx-quic/patch/nginx+quic.patch
With the patch applied, we can configure and build Nginx. The key is to include the ngx_http_v3_module and ensure SSL is enabled.
./configure \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--modules-path=/usr/lib/x86_64-linux-gnu/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--user=nginx \
--group=nginx \
--with-compat \
--with-file-aio \
--with-threads \
--with-http_addition_module \
--with-http_auth_request_module \
--with-http_dav_module \
--with-http_flv_module \
--with-http_gunzip_module \
--with-http_gzip_module \
--with-http_mp4_module \
--with-http_random_index_module \
--with-http_realip_module \
--with-http_secure_link_module \
--with-http_slice_module \
--with-http_ssl_module \
--with-http_stub_status_module \
--with-http_sub_module \
--with-http_v2_module \
--with-http_v3_module \
--with-stream \
--with-stream_realip_module \
--with-stream_ssl_module \
--with-stream_ssl_preread_module \
--with-pcre \
--with-pcre-jit \
--with-zlib \
--with-openssl=../openssl-3.1.4
make
sudo make install
You’ll need to ensure you have an OpenSSL version that supports TLS 1.3. The patch typically relies on OpenSSL 3.x. If you don’t have it, you’ll need to build and install it separately. The --with-openssl flag points to the directory where you’ve extracted OpenSSL.
After installation, we need to configure Nginx to listen on UDP port 443 for QUIC connections and to present the necessary HTTP/3 configuration.
Edit your nginx.conf (usually /etc/nginx/nginx.conf or a file in /etc/nginx/conf.d/) and add or modify your server block.
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
server_name your_domain.com;
root /var/www/html;
index index.html index.htm;
ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# HTTP/3 specific settings
add_header Alt-Svc 'h3=":443"; ma=86400';
add_header QUIC-Status $quic; # Optional: for debugging
location / {
try_files $uri $uri/ =404;
}
}
The listen 443 quic reuseport; directive tells Nginx to listen for QUIC connections on UDP port 443. The reuseport option is important for performance, allowing multiple worker processes to bind to the same port. The add_header Alt-Svc 'h3=":443"; ma=86400'; is the crucial Alt-Svc (Alternative Services) header. This tells browsers that the same content is available over HTTP/3 on UDP port 443. ma=86400 specifies the maximum age (in seconds) for this information.
Once configured, reload Nginx:
sudo systemctl reload nginx
To verify, you can use tools like curl with HTTP/3 support (e.g., curl --http3 https://your_domain.com) or check your browser’s network tab. You’ll see the Alt-Svc header being sent.
The magic behind the Alt-Svc header is that it allows the browser to discover the availability of HTTP/3. When a browser first connects to your server via HTTP/2 or HTTP/1.1, it sees this header and then attempts to establish a new connection using HTTP/3. If successful, subsequent requests will use HTTP/3.
The next hurdle you’ll likely encounter is ensuring your firewall is configured to allow UDP traffic on port 443. If you’re using ufw, you’ll need:
sudo ufw allow 443/udp