Skip to content

SpeedPart of: HTTP/2 and HTTP/3

How to enable HTTP/2 and HTTP/3 on nginx, Apache, Caddy and Cloudflare

How to enable HTTP/2 and HTTP/3: the exact nginx, Apache, Caddy, LiteSpeed, IIS and Cloudflare settings, the firewall rule HTTP/3 needs, and how to check that browsers really use them.

getReport teamUpdated 26 Sept 202612 min read

To enable HTTP/2 in nginx 1.25.1 or newer, add http2 on; to the server block that listens on port 443 with TLS; older versions use listen 443 ssl http2;. HTTP/3 in nginx needs a listen 443 quic line, an Alt-Svc header and UDP port 443 open in the firewall. Apache needs mod_http2, the event MPM and Protocols h2 http/1.1; Caddy and LiteSpeed have both protocols on by default; Cloudflare has HTTP/2 on and HTTP/3 behind one switch. All of it requires HTTPS.

This guide is for whoever runs the server or the CDN account: the settings for each common server and host, the traps that make a config look right while browsers still use HTTP/1.1, and how to verify. Why the protocols matter is covered in the guide to HTTP/2 and HTTP/3.

Quick answer

Server or serviceHTTP/2HTTP/3
nginx 1.25.1+http2 on; in the TLS server blocklisten 443 quic reuseport; + Alt-Svc header + UDP 443 open (module still labelled experimental)
nginx before 1.25.1listen 443 ssl http2;Upgrade first
Apache 2.4.17+a2enmod http2, event MPM, Protocols h2 http/1.1Not in the stable server; use a CDN or proxy in front
Caddy 2On by defaultOn by default; open UDP 443
LiteSpeed / OpenLiteSpeedOn by defaultOn by default; open UDP 443
IIS on Windows Server 2016+On by default over TLSWindows Server 2022 and newer, enabled with registry keys
CloudflareOn by defaultSpeed → Settings → Protocol Optimization → HTTP/3

Then run the free HTTP/2 and HTTP/3 test: the ALPN row should say h2, and the Alt-Svc row should show h3 if you enabled HTTP/3.

Before you start

Three things are true for every server:

  1. HTTPS first. Browsers only speak HTTP/2 over TLS, and HTTP/3 has TLS 1.3 built in. If the site still answers on http:// only, set up a certificate first. Use TLS 1.2 or newer; HTTP/2 forbids some older cipher suites, and current server defaults already avoid them.
  2. ALPN decides. The browser and server agree on HTTP/2 during the TLS handshake through the ALPN extension. The server's TLS library must support it; every maintained OpenSSL version does.
  3. Keep HTTP/1.1 and HTTP/2 on. HTTP/3 is always an addition. The first visit usually arrives over HTTP/2, and networks that block UDP fall back to it.

Check what you have now:

The test makes a TLS handshake offering h2 and http/1.1, records which one the server picks and the TLS version, reads the page's Alt-Svc header, and loads the page in Chromium to count which protocol every file actually used, including third-party files.

nginx: enable HTTP/2

Check your version and that the HTTP/2 module is built in (distribution and nginx.org packages include it):

Shell
nginx -v
nginx -V 2>&1 | grep -o with-http_v2_module

nginx 1.25.1 and newer use a separate http2 directive:

nginx
# /etc/nginx/sites-available/example.com (or conf.d/example.com.conf)
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;

    server_name example.com www.example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    # …
}

Older nginx puts it on the listen line:

nginx
listen 443 ssl http2;
listen [::]:443 ssl http2;

On 1.25.1 and newer, that older form still works but logs a warning that the listen … http2 parameter is deprecated; switch to http2 on; when you upgrade.

Two nginx details catch people out:

  • Set it in every TLS server block. With the old listen … http2 parameter, one block could switch HTTP/2 on for every site sharing that address and port; with http2 on; each server block has its own setting. Turning it on in all of them avoids one site quietly staying on HTTP/1.1.
  • Port 80 does not matter. Browsers never use HTTP/2 on plain http://. Your port 80 block should only redirect to HTTPS.

Test the configuration and reload:

Shell
sudo nginx -t && sudo systemctl reload nginx

The nginx configuration guide has a complete server block with TLS settings and security headers.

nginx: enable HTTP/3

nginx supports HTTP/3 since 1.25.0, through the ngx_http_v3_module. The nginx documentation still labels it experimental, and it needs an SSL library with QUIC support; the packages from nginx.org are built with it. Check:

Shell
nginx -V 2>&1 | grep -o with-http_v3_module

Add a QUIC listener next to the TLS one, and advertise it:

nginx
server {
    listen 443 quic reuseport;
    listen 443 ssl;
    listen [::]:443 quic reuseport;
    listen [::]:443 ssl;
    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;
    ssl_protocols TLSv1.2 TLSv1.3;

    add_header Alt-Svc 'h3=":443"; ma=86400' always;
    # …
}
  • reuseport may appear on only one listen … quic per address and port across all server blocks. Other server blocks on the same port use listen 443 quic; without it.
  • The certificate is the same. HTTP/3 uses the site's normal certificate; there is nothing extra to buy or issue.
  • ssl_early_data on; enables 0-RTT, which saves a round trip on repeat visits but lets an attacker replay the first request. Leave it off unless your application treats replayed GET requests safely.
  • add_header in a location replaces headers set at the server level. If a location has its own add_header lines, repeat the Alt-Svc header there.

Then open UDP 443. On the server:

Shell
sudo ufw allow 443/udp          # ufw
sudo firewall-cmd --permanent --add-port=443/udp && sudo firewall-cmd --reload   # firewalld

Cloud providers also filter traffic before it reaches the server: add UDP 443 to the security group or network firewall, and check that any load balancer in front passes UDP through to the same server for the whole connection. Advertising h3 while UDP 443 is closed is worse than not advertising it: browsers try QUIC, wait, and fall back.

Apache: enable HTTP/2

Apache 2.4.17 and newer ship mod_http2. It needs a threaded MPM: with the old prefork MPM, Apache accepts the configuration but HTTP/2 works badly or not at all. On Debian and Ubuntu:

Shell
sudo a2dismod php8.3 mpm_prefork        # use your PHP version; mod_php requires prefork
sudo a2enmod mpm_event http2 proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2

Then allow HTTP/2 in the TLS virtual host, or globally in apache2.conf:

Apache
<VirtualHost *:443>
    Protocols h2 http/1.1
    ServerName example.com
    # SSLEngine on, certificates …
</VirtualHost>

h2 is HTTP/2 over TLS. You may see h2c in examples; that is HTTP/2 without TLS, which browsers do not use. Run apachectl configtest before restarting.

On RHEL-family systems, mod_http2 is in the mod_http2 package and the MPM is chosen in /etc/httpd/conf.modules.d/00-mpm.conf. Remove any H2Push lines from old configurations: browsers no longer use server push.

HTTP/3 on Apache: the stable Apache HTTP Server does not support HTTP/3. Put a CDN or a reverse proxy that speaks HTTP/3 (such as nginx, Caddy or a load balancer) in front, and let it talk to Apache over HTTP/1.1 or HTTP/2.

Caddy and LiteSpeed

Caddy enables HTTP/1.1, HTTP/2 and HTTP/3 by default on every site with automatic HTTPS, and sends the Alt-Svc header itself. The only thing to do is open UDP 443 in the firewall. See Caddy, the two-line HTTPS server.

LiteSpeed Web Server and OpenLiteSpeed have HTTP/2 and HTTP/3 on by default for TLS listeners; the settings are under the listener or server configuration in the admin console if they were turned off. Many shared hosts run LiteSpeed, which is why HTTP/3 often works on cheap hosting without any action. Open UDP 443 if you run your own.

IIS on Windows Server

IIS on Windows Server 2016 and newer negotiates HTTP/2 automatically over TLS; nothing needs enabling. HTTP/3 is available from Windows Server 2022, switched on with two registry values under HKLM\SYSTEM\CurrentControlSet\Services\HTTP\Parameters: EnableHttp3 and EnableAltSvc, both set to 1, followed by a restart. It needs TLS 1.3 and UDP 443 open in Windows Firewall. Check Microsoft's documentation for your version before changing the registry.

Cloudflare and other CDNs

A CDN terminates the browser's connection, so the protocol visitors get is the CDN's, whatever your origin speaks.

  • Cloudflare: HTTP/2 is on by default for every plan. HTTP/3 is under Speed → Settings → Protocol Optimization → HTTP/3 (with QUIC); Cloudflare moves menus from time to time, so search the dashboard for "HTTP/3" if it is not there. 0-RTT connection resumption is a separate toggle. "HTTP/2 to Origin" controls the connection from Cloudflare to your server, which is optional. Cloudflare settings for speed and security covers the rest of the dashboard.
  • Other CDNs and managed hosts (Fastly, Netlify, Vercel, most managed WordPress hosts) serve HTTP/2 by default, and many offer HTTP/3. Look for "HTTP/3" or "QUIC" in the settings or ask support.

If your hosting panel has no HTTP/2 option and support cannot enable it, putting a CDN in front is the simplest way to give visitors HTTP/2 and HTTP/3.

Verify that browsers really use them

Run the test again after each change:

  • The ALPN row should read negotiated h2. "Server chose http/1.1" means the change did not take effect for the handshake: check the MPM on Apache, the other server blocks on nginx, or a proxy in front.
  • The Alt-Svc row should show h3=":443" if you enabled HTTP/3.
  • The per-file protocol counts show what the browser used; files still on http/1.1 usually come from third-party hosts.

From a terminal:

Shell
curl -sI --http2 https://example.com/ | head -1        # HTTP/2 200
curl -sI --http3-only https://example.com/ | head -1   # HTTP/3 200 (needs a curl built with HTTP/3)
openssl s_client -connect example.com:443 -alpn h2 </dev/null 2>/dev/null | grep ALPN

In the browser, add the Protocol column in developer tools → Network. Expect h2 on the first load and h3 after a reload once HTTP/3 is advertised, because browsers only try QUIC after they have seen the Alt-Svc header. The QUIC guide explains why.

If visitors report errors after the change, see ERR_HTTP2_PROTOCOL_ERROR or ERR_QUIC_PROTOCOL_ERROR; the second is almost always UDP 443 or an Alt-Svc header on a server that does not speak QUIC.

Common mistakes

  • Enabling HTTP/2 on port 80. Browsers ignore it; only the TLS listener counts.
  • Apache with prefork. Protocols h2 is accepted, but HTTP/2 does not work properly; switch to the event MPM and PHP-FPM.
  • One nginx server block left without HTTP/2 while the others have it, so one site stays on HTTP/1.1.
  • Advertising h3 with UDP 443 closed, at the server or in the cloud firewall.
  • reuseport on several server blocks, which stops nginx from starting.
  • Turning 0-RTT on by default without considering replayed requests.
  • Keeping old server push directives. They do nothing useful now.

Questions people ask

How do I enable HTTP/2 in nginx?

On nginx 1.25.1 or newer, add http2 on; inside the server block that has listen 443 ssl;, then run nginx -t and reload. On older versions, add http2 to the listen line: listen 443 ssl http2;. The site must use HTTPS, because browsers never use HTTP/2 on plain HTTP. Turn it on in every TLS server block, so no site on the server stays on HTTP/1.1.

Why does nginx say the listen http2 directive is deprecated?

Since nginx 1.25.1, HTTP/2 is switched on with its own http2 on; directive instead of a parameter on the listen line. The old listen 443 ssl http2; form still works but logs a deprecation warning when nginx starts or reloads. Remove http2 from the listen lines and add http2 on; to the server block to silence it; the behaviour is the same.

Do I need HTTP/2 on my origin server if I use Cloudflare?

Not for visitors. Cloudflare terminates the browser's connection and serves HTTP/2, and HTTP/3 if you switch it on, whatever your origin speaks. The connection from Cloudflare to your server can stay on HTTP/1.1; the optional "HTTP/2 to Origin" setting changes that hop. Enabling HTTP/2 on the origin still helps anyone who reaches it directly and costs nothing on a modern server.

Why does my site still use HTTP/1.1 after enabling HTTP/2?

Usually because something else answers the TLS handshake. On Apache, the prefork MPM or a missing Protocols line in the right virtual host; on nginx, the setting added to a different server block than the one that answers, or a config that was never reloaded. A load balancer or proxy in front may also terminate TLS with HTTP/1.1 only. An HTTP/2 test shows which protocol the handshake actually picks.

Does HTTP/3 need a separate SSL certificate?

No. HTTP/3 uses the same certificate as HTTPS over HTTP/2; QUIC carries a normal TLS 1.3 handshake. What it needs in addition is a QUIC listener on UDP port 443, that port open in every firewall between the internet and the server, and an Alt-Svc header so browsers know to try it. Keep HTTP/2 running beside it for first visits and blocked networks.

Check your site before and after Check