Intro
If you are sort of into servers, websites etc, you probably heard of HTTP/3. It’s a shiny new HTTP protocol that have performance improvements, and brand new.
Your browser probably supported HTTP/3 somewhat, as now Firefox, Safari and Chromium-based browsers all have some support by now (Info provided by CanIUse.com).
However, in terms of web servers, not too many open-source server software support it. Yeah, some big providers like AWS Cloudfront, Cloudflare, Fastly (These are just a couple providers I can come up in my head in a quick second) do offer support way ahead, even LiteSpeed Web Server does. Open-source are often lagged behind for several reasons, and by support I only mean if Apache and Nginx are officially supporting the feature in their builds, not third party support.
To learn more about HTTP/3, search it on Google (or your preferred search engine, or wherever you get your information from).
Convenient links to articles: What is HTTP/3? by Cloudflare, HTTP/3 on Wikipedia, HTTP3 by Bunny.net
Nginx recently announced experimental HTTP/3 support for their mainline build 1.25.0. although there are some blog and tutorials already on how to enable it, I still got stuck on one part, so here’s my guide on how to enable it.
Prerequisites
Check if your Nginx support HTTP/3
You’ll need a Nginx server with HTTP/3 support. According to Nginx website, you still need the module ngx_http_v3_module
to show up in your Nginx build before you can use it.
How to check? Type nginx -V
and expect output like
nginx version: nginx/1.25.0
built with OpenSSL 3.0.2 15 Mar 2022
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -ffile-prefix-map=/build/nginx-v0f2Wg/nginx-1.25.0=. -flto=auto -ffat-lto-objects -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -flto=auto -Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-compat --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_v3_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_flv_module --with-http_geoip_module=dynamic --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_mp4_module --with-http_perl_module=dynamic --with-http_random_index_module --with-http_secure_link_module --with-http_sub_module --with-http_xslt_module=dynamic --with-mail=dynamic --with-mail_ssl_module --with-stream=dynamic --with-stream_geoip_module=dynamic --with-stream_ssl_module --with-stream_ssl_preread_module --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/brotli --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/http-headers-more-filter --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/http-auth-pam --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/http-cache-purge --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/http-dav-ext --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/http-ndk --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/http-echo --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/http-fancyindex --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/http-geoip2 --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/nchan --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/http-lua --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/rtmp --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/http-uploadprogress --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/http-upstream-fair --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/http-subs-filter --add-dynamic-module=/build/nginx-v0f2Wg/nginx-1.25.0/debian/modules/ssl-ct
(The output might be different due to packages your Nginx build includes, but at least you should see --with-http_v3_module
)
Install Nginx from PPA
If you don’t see a http_v3 module, your Nginx might not be up to date, or it just simply don’t have it.
A simple way to get latest Nginx without compiling it yourself (you might lose some features) is to use the PPA provided by Ondrej Sury
To install his PPA, run the following in your Ubuntu terminal:
sudo add-apt-repository ppa:ondrej/nginx-mainline
sudo apt update
WARNING: This PPA only supports active Ubuntu LTS version
After the PPA is added and apt cache flushed, you should be able to install Nginx normally (using APT as package manager)
sudo apt install nginx
Check your output again, if you found the required module, you can proceed to next step.
Configuration
The complete setup below might be different than whatever you have, so here’s a short guide
- Specify
listen 443 quic;
in your server block (Yes, if you also use IPv6, thenlisten [::]:443 quic;
) - Ensure
TLSv1.3
is in your supportedssl_protocols
for this server block. - Add the header
add_header Alt-Svc 'h3=":$server_port"; ma=86400';
in the same block, indicating your website support HTTP/3 (This is carried over from QUIC, I believe suggests that the connection is available at443/udp
(or whichever your server port is). - Additional debug: You can setup an additional custom header
add_header X-protocol $server_protocol always;
to let your web server tell you which protocol you used.
Some sources:
Nginx.com: https://www.nginx.com/blog/binary-packages-for-preview-nginx-quic-http3-implementation/#configuration
Nginx.org: https://nginx.org/en/docs/http/ngx_http_v3_module.html
Mozilla SSL config: https://ssl-config.mozilla.org/#server=nginx&version=1.25.0&config=intermediate&openssl=3.0.2&guideline=5.7
You can have more granular control on how HTTP3 works on the server or the server block, see the above Nginx.org link for more information.
Setup default server block
Default block for all incoming http (port 80) requests
This will simply redirect them to its HTTPS version on port 443.
server {
listen 80 default_server;
listen [::]:80 default_server;
location / {
return 301 https://$host$request_uri;
}
}
Default block for all incoming https (port 443) requests that did not match any additional server blocks you configured.
This block have http2, http3 enabled and set as the default fallback.
SSL Config generated with Mozilla SSL Config, using Nginx version 1.25.0, OpenSSL version 3.0.2, Guideline 5.7 and Compatibility level Intermediate.
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
listen 443 quic reuseport default_server;
listen [::]:443 quic reuseport default_server;
ssl_certificate /path/to/signed_cert_plus_intermediates;
ssl_certificate_key /path/to/private_key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
ssl_dhparam /path/to/dhparam;
# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
# Enable QUIC and HTTP/3
ssl_quic on;
ssl_early_data on;
add_header Alt-Svc 'h3=":$server_port"; ma=86400';
# HSTS (ngx_http_headers_module is required) (63072000 seconds)
add_header Strict-Transport-Security "max-age=63072000" always;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;
# replace with the IP address of your resolver
resolver 127.0.0.1;
}
Setup secondary server block
You only need to specify reuseport
in a listen directive once. Or you’ll receive some errors like duplicate listen options
.
One listen directive means one unique address:Port combo (Like 127.0.0.1:443, 0.0.0.0:443, 0.0.0.0:8843, [::]:443, [::]:8843)
server {
# for better compatibility we recommend
# using the same port number for QUIC and TCP
listen 443 quic; # QUIC
listen 443 ssl http2; # TCP
listen [::]:443 quic; # QUIC
listen [::]:443 ssl http2; # TCP
# replace this with your actual domain name
server_name additional_domain_hostname;
ssl_certificate /path/to/signed_cert_plus_intermediates;
ssl_certificate_key /path/to/private_key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
ssl_dhparam /path/to/dhparam;
# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
# Enable QUIC and HTTP/3
ssl_quic on;
ssl_early_data on;
add_header Alt-Svc 'h3=":$server_port"; ma=86400';
# HSTS (ngx_http_headers_module is required) (63072000 seconds)
add_header Strict-Transport-Security "max-age=63072000" always;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;
# replace with the IP address of your resolver
resolver 127.0.0.1;
}
Optional setups
debug
Add the following header to see if the requests is answered in h3.
# signal whether we are using QUIC+HTTP/3
add_header X-protocol $server_protocol always;
Common Issue
References
I tried my best to cite the sources I obtain information from in text… So go find them!
If you have a question, leave a comment or ask somewhere else. I’ll try to get back to you, but I’m not very knowledgeable.