HTTP/2 is actually slower than HTTP/1.1 for some traffic patterns, but its benefits in multiplexing and header compression make it a net win for most modern web applications.

Let’s see it in action. Imagine a browser requesting a complex webpage with dozens of small assets (images, CSS, JS) from a server.

With HTTP/1.1, each asset requires a separate TCP connection, or if using keep-alive, a separate request-response cycle over a single connection, leading to head-of-line blocking. The browser has to wait for one asset to download before it can even start requesting the next one on that connection.

Browser -> Server: GET /style.css HTTP/1.1
Server -> Browser: 200 OK (style.css content)
Browser -> Server: GET /script.js HTTP/1.1
Server -> Browser: 200 OK (script.js content)
Browser -> Server: GET /image1.jpg HTTP/1.1
Server -> Browser: 200 OK (image1.jpg content)
... and so on for dozens of requests.

Now, with HTTP/2, the same webpage is requested over a single TCP connection.

Browser -> Server: PRI * HTTP/2.0
Server -> Browser: PRI * HTTP/2.0 (settings frame)
Browser -> Server: SETTINGS (client settings)
Server -> Browser: SETTINGS (server settings)
Browser -> Server: HEADERS (request for /style.css, stream 1)
Server -> Browser: HEADERS (response for /style.css, stream 1)
Server -> Browser: DATA (style.css content, stream 1)
Browser -> Server: HEADERS (request for /script.js, stream 3)
Server -> Browser: HEADERS (response for /script.js, stream 3)
Server -> Browser: DATA (script.js content, stream 3)
Browser -> Server: HEADERS (request for /image1.jpg, stream 5)
Server -> Browser: HEADERS (response for /image1.jpg, stream 5)
Server -> Browser: DATA (image1.jpg content, stream 5)
... all interleaved on the same connection.

HTTP/2 works by framing the HTTP message into smaller units called "frames." These frames can represent header information, data, or other control signals. Multiple requests and responses are then multiplexed over a single TCP connection, each assigned a unique "stream ID." This eliminates head-of-line blocking at the HTTP level because a stalled request on one stream doesn’t prevent progress on other streams. Additionally, HPACK compression dramatically reduces the overhead of HTTP headers, which can be substantial for many small requests.

The primary problem HTTP/2 solves is the inefficiency of HTTP/1.1 in handling the modern web’s demand for numerous small assets. HTTP/1.1’s reliance on multiple TCP connections or its head-of-line blocking on a single connection creates significant latency. HTTP/2’s multiplexing and header compression directly address these bottlenecks.

Here’s a typical configuration snippet for Nginx to enable HTTP/2:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    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;
    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;
    ssl_prefer_server_ciphers off;

    # ... rest of your server configuration
}

The http2 directive on the listen directive is the key. This tells Nginx to enable HTTP/2 on this port. For SSL/TLS, this is typically done over the https port (443). You’ll also want robust SSL/TLS configurations as HTTP/2 over TLS (h2) is the de facto standard.

The most surprising thing is that while HTTP/2 is faster overall, it can sometimes be slower for a single, very large file download compared to HTTP/1.1 with a well-tuned TCP connection. This is because HTTP/2’s multiplexing and framing overhead, while beneficial for many small requests, adds a slight penalty to the simpler, direct transfer of a single large object. The true power is unlocked when you have many concurrent requests.

When configuring for HTTP/2, pay close attention to your ssl_protocols and ssl_ciphers. While HTTP/2 itself can theoretically run over unencrypted connections (h2c), browser support for unencrypted HTTP/2 is virtually non-existent. All major browsers require TLS for HTTP/2. Ensure you are using modern TLS versions (TLSv1.2 and TLSv1.3) and strong, forward-secrecy-enabled ciphers. Older protocols like TLSv1.0 and TLSv1.1, and weak ciphers, not only compromise security but also significantly undermine HTTP/2’s performance benefits due to their inherent inefficiencies.

The next challenge is optimizing for HTTP/3 and QUIC.

Want structured learning?

Take the full Http2 course →