# 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.

Updated 2026-09-26 · Speed & Core Web Vitals · HTML version: https://getreport.app/guides/how-to-enable-http2-and-http3

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](https://getreport.app/guides/http2-and-http3).

## Quick answer

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

Then run the free [HTTP/2 and HTTP/3 test](https://getreport.app/tools/http2-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:

> **Free tool:** [HTTP/3 test and HTTP/2 check for any website](https://getreport.app/tools/http2-test): Free HTTP/3 test and HTTP/2 check: see which protocol your server negotiates, whether HTTP/3 is advertised, and which files still load over HTTP/1.1.

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.

> **Check: HTTP/2 server support.** Without HTTP/2 the browser opens up to six connections and queues everything else. Turning it on is a server or CDN setting and makes pages with many files load faster with no code change.
>
> 1. Enable HTTP/2 in your web server (nginx 1.25.1+: "http2 on;" in the server block, older: "listen 443 ssl http2"; Apache mod_http2) or put the site behind a CDN that speaks it.
> 2. Check the host's control panel; on Cloudflare, Netlify, Vercel and most managed WordPress hosts it is already on.

## nginx: enable HTTP/2

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

```bash
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:

```bash
sudo nginx -t && sudo systemctl reload nginx
```

The [nginx configuration guide](https://getreport.app/guides/nginx-configuration-for-an-a-grade) 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:

```bash
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:

```bash
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:

```bash
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](https://getreport.app/guides/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](https://getreport.app/guides/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.

> **Check: A CDN is in front of the site.** A CDN serves your files from a location near each visitor and absorbs traffic spikes. Without one, visitors far from your server wait longer for every file.
>
> 1. Put the site behind a CDN such as Cloudflare, Bunny or Fastly; the free tiers cover most small sites.
> 2. At minimum, serve images and static files from a CDN hostname.

## Verify that browsers really use them

Run the test again after each change:

> **Check: Every request uses HTTP/2 or HTTP/3.** HTTP/2 and HTTP/3 load many files over one connection at once; HTTP/1.1 queues them six at a time. Switching usually makes a page with many images or scripts noticeably faster with no code change.
>
> 1. Turn on HTTP/2 or HTTP/3 in your hosting or CDN panel; on Cloudflare and most managed hosts it is a checkbox.
> 2. Move assets that are still served over HTTP/1.1 (see the technical detail) to a host that supports HTTP/2.

> **Check: HTTP/3 is advertised via Alt-Svc.** HTTP/3 (QUIC) shortens the connection setup and copes better with flaky mobile networks. Servers announce it with an Alt-Svc header; without it browsers never try.
>
> 1. Enable HTTP/3 in your CDN or server (Cloudflare toggle, Caddy default, Nginx 1.25+ "listen 443 quic").
> 2. Make sure UDP port 443 is open in the firewall.

- 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:

```bash
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](https://getreport.app/guides/what-is-quic-http3) explains why.

If visitors report errors after the change, see [ERR_HTTP2_PROTOCOL_ERROR](https://getreport.app/guides/err-http2-protocol-error) or [ERR_QUIC_PROTOCOL_ERROR](https://getreport.app/guides/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.
