HTTP/2

Protocol

Version of HTTP that carries many concurrent request/response streams over one TCP connection and compresses header fields with HPACK, so clients no longer need parallel connections. HTTP semantics are unchanged and TCP head-of-line blocking remains. RFC 9113; ALPN token "h2" over TLS.

Also known as h2.

12 min read Updated Aug 30, 2026

Full Explanation

HTTP/2 is a major version of the Hypertext Transfer Protocol. It is defined by RFC 9113, which obsoletes RFC 7540. It keeps HTTP semantics unchanged. It uses the same methods, status codes, header fields and URIs as HTTP/1.1. It changes only how they are put on the wire. A binary framing layer carries many concurrent request/response exchanges as separate streams over a single TCP connection. HPACK compression also shrinks the repeated header fields. RFC 9113 calls it "an optimized expression of the semantics" of HTTP (section 1). It removes the reason HTTP/1.0 and HTTP/1.1 clients opened several parallel connections to one server.

Three things HTTP/2 is not. It is not a new set of semantics. Nothing about caching, methods or status codes changes when you enable it. It is not a new transport. It runs over the same TCP and TLS stack. RFC 9113 says so about its own design: "Note, however, that TCP head-of-line blocking is not addressed by this protocol" (section 1). So one lost segment still stalls every stream on the connection. Fixing that took a different transport: that is what HTTP/3 does over QUIC. And it is not the newest version of HTTP. HTTP/2 and HTTP/3 coexist, and h2 is normally the connection over which a client learns h3 is available. Over TLS it is identified by the string "h2". That string is used in the ALPN extension "and in any place where HTTP/2 over TLS is identified" (section 3.1), which is why curl and browser dev tools label it that way.

How it works

HTTP/2 is a connection-oriented application-layer protocol that runs over a TCP connection (RFC 9113 section 2). The pieces fit together in this order.

  • Version negotiation. A client making a request to an "https" URI uses TLS with the ALPN extension. It offers the "h2" identifier. The server selects it if it can (section 3.2). This is not optional: "HTTP/2 connections over TLS MUST use protocol negotiation in TLS" (section 3.3). A cleartext start also exists. Here a client that already knows the server speaks HTTP/2 connects and sends the connection preface directly. This is called prior knowledge, which "only affects the establishment of HTTP/2 connections over cleartext TCP" (section 3.3). The old "h2c" Upgrade handshake is gone: that usage "was never widely deployed and is deprecated by this document" (section 3.1).
  • Frames and streams. The basic protocol unit is a frame. HEADERS and DATA frames form requests and responses. SETTINGS, WINDOW_UPDATE, RST_STREAM, GOAWAY and PUSH_PROMISE carry protocol machinery. "Multiplexing of requests is achieved by having each HTTP request/response exchange associated with its own stream". "Streams are largely independent of each other, so a blocked or stalled request or response does not prevent progress on other streams" (section 2, section 5). Read the qualifier carefully. That independence holds at the HTTP layer, because TCP underneath still carries one ordered byte stream.
  • Concurrency is bounded, and each side sets its own bound. A peer limits how many streams the other may open with SETTINGS_MAX_CONCURRENT_STREAMS. The limit is directional. Clients state what the server may initiate. Servers state what the client may initiate. The specification recommends "that this value be no smaller than 100, so as to not unnecessarily limit parallelism" (section 5.1.2, section 6.5.2). Multiplexed never means unlimited. A client that oversteps has the stream refused, not queued.
  • Flow control. Multiplexing "introduces contention over use of the TCP connection, resulting in blocked streams". So HTTP/2 adds credit-based windows advertised with WINDOW_UPDATE: "Flow control is used for both individual streams and the connection as a whole" (section 5.2). Each stream's window starts at 2^16-1 (65,535) octets. That is the initial value of SETTINGS_INITIAL_WINDOW_SIZE (section 6.5.2). Flow control operates between the endpoints of a single hop, not end to end (section 5.2.1).
  • HPACK compresses the header fields. HPACK "uses two tables for associating header fields to indexes". One is a predefined static table of common fields. The other is a dynamic table the encoder fills with fields repeated across the connection (RFC 7541 section 2.3). So a recurring cookie, user-agent or host travels as an index instead of as bytes. The state is per connection and per direction. The dynamic table starts at 4,096 octets, the initial value of SETTINGS_HEADER_TABLE_SIZE (RFC 9113 section 6.5.2). It exists because "the redundant header fields in these requests unnecessarily consume bandwidth, measurably increasing latency" (RFC 7541 section 1).
  • Priority signalling was dropped, not fixed in place. "RFC 7540 defined a rich system for signaling priority of requests. However, this system proved to be complex, and it was not uniformly implemented" (section 5.3.1). RFC 9113 deprecates that scheme. It retains only enough of the frame fields to stay interoperable. It points endpoints that need priority at a different scheme: the Priority header field and PRIORITY_UPDATE frames of RFC 9218 (section 5.3.2). Prioritisation still matters. The original way of asking for it does not work.
  • One connection can serve several hostnames. Connections to an origin server "MAY be reused for requests with multiple different URI authority components". For "https" resources, that reuse "additionally depends on having a certificate that is valid for the host in the URI". RFC 9113 notes: "A single certificate can be used to establish authority for multiple origins" (section 9.1.1). A server that does not want the coalescing answers 421 (Misdirected Request).

Why it matters for a CDN

  • Fewer, longer-lived connections carry the same page. "The resulting protocol is more friendly to the network because fewer TCP connections can be used in comparison to HTTP/1.x. This means less competition with other flows and longer-lived connections, which in turn lead to better utilization of available network capacity" (RFC 9113 section 1). At an edge that means fewer handshakes, a congestion window that stays warm, and fewer sockets per viewer.
  • Header bytes shrink on exactly the traffic a CDN carries most. That traffic is large numbers of small requests. Compressing the field block "has especially advantageous impact upon request sizes in the common case, allowing many requests to be compressed into one packet" (RFC 9113 section 2).
  • Connection coalescing makes domain sharding counterproductive. Splitting assets across asset1.example.com and asset2.example.com existed to win parallel HTTP/1.1 connections. Under HTTP/2 it forces extra connections instead. The exception: if one edge certificate covers all the names, a client may coalesce them back onto one connection anyway (RFC 9113 section 9.1.1).
  • There are two independent legs. Client-to-edge and edge-to-origin are separate connections, negotiated separately. What the browser shows tells you nothing about the origin leg. Vendors differ there: the origin leg is a configuration question, not a property of HTTP/2.

What CDNs do

HTTP/2 to clients is effectively the default everywhere. The meaningful differences are how it is switched and what happens on the origin leg.

  • Cloudflare, client leg. "HTTP/2 is enabled by default for all plans (though it does require an SSL certificate at Cloudflare's edge network)." Free-plan domains cannot turn it off. The availability table marks Free as not customisable, while Pro, Business and Enterprise are (Cloudflare, HTTP/2).
  • Cloudflare, origin leg. "At Cloudflare, HTTP/2 connection to the origin is enabled by default". Multiple streams share a long-lived TCP connection to the origin. Cloudflare notes that "if the origin does not support HTTP/2, Cloudflare will initiate an HTTP/1.1 connection", selected by ALPN. The per-connection stream cap depends on plan. It is configurable only on Enterprise. Cloudflare "honors your origin's SETTINGS_MAX_CONCURRENT_STREAMS, allowing your server to enforce stricter limits". Treat the cap as something to read from your zone, not a constant. The same page states 200 streams per connection for Free, Pro and Business in its plan table. It also states "up to 100 concurrent streams by default" in its configuration section (Cloudflare, HTTP/2 to Origin).
  • AWS CloudFront, client leg. The viewer-facing version is a per-distribution setting, HttpVersion. Valid values are http1.1, http2, http3 and http2and3: "The default value for new web distributions is http2. Viewers that don't support HTTP/2 automatically use an earlier HTTP version" (CloudFront DistributionConfig). A viewer only gets HTTP/2 if it supports TLSv1.2 or later and SNI (CloudFront distribution settings).
  • AWS CloudFront, origin leg. "CloudFront forwards requests to your custom origin using HTTP/1.1" (CloudFront request behaviour for custom origins). The documented exception is gRPC, "an open-source remote procedure call (RPC) framework built on HTTP/2". CloudFront proxies gRPC straight to the origin once HTTP/2 is among the distribution's supported versions and the origin serves HTTPS (Using gRPC with CloudFront distributions). A blanket claim that CloudFront never speaks HTTP/2 to an origin is therefore no longer accurate.
  • Fastly. There is no separate HTTP/2 switch: "Fastly services will automatically support and negotiate HTTP/2 at the protocol level if your chosen TLS configuration supports it" (Fastly, Alt-Svc). So the protocol follows the TLS configuration attached to the domain.

Watch out for

  • Transport head-of-line blocking is still there. HTTP/2's parallelism is invisible to TCP loss recovery. So "a lost or reordered packet causes all active transactions to experience a stall regardless of whether that transaction was directly impacted by the lost packet" (RFC 9114 section 1.1). RFC 9113 states this same limit about itself in section 1. On a lossy mobile last mile, one multiplexed connection can behave worse than several HTTP/1.1 connections.
  • Cheap stream creation is a denial-of-service surface. CVE-2023-44487, HTTP/2 Rapid Reset, is a protocol-level flaw: "The HTTP/2 protocol allows a denial of service (server resource consumption) because request cancellation can reset many streams quickly, as exploited in the wild in August through October 2023" (NVD, CVE-2023-44487). Cloudflare measured attacks peaking just above 201 million requests per second from a botnet of roughly 20,000 machines. Cloudflare warns that "stream concurrency on its own cannot mitigate rapid reset". The effective defence is to watch client-sent RST_STREAM frames and close abusive connections (Cloudflare, technical breakdown). RFC 9113 had already noted that an HTTP/2 connection "can demand a greater commitment of resources to operate than an HTTP/1.1 connection" (section 10.5). Any edge terminating HTTP/2 needs that DDoS mitigation in place.
  • Server push is effectively dead. Never make a page depend on it. RFC 9113 is blunt: "server push is difficult to use effectively, because it requires the server to correctly anticipate the additional requests the client will make". A client can also switch it off: "The SETTINGS_ENABLE_PUSH setting can be set to 0 to indicate that server push is disabled" (section 8.4). Chromium went further: "support of HTTP/2 Server Push will be disabled by default in Chrome 106 and other Chromium-based browsers in their next releases" (Chrome for Developers). Use 103 Early Hints or preload instead.
  • Connection-specific header fields make a message malformed. An endpoint must not generate an HTTP/2 message containing the Connection, Proxy-Connection, Keep-Alive, Transfer-Encoding or Upgrade header fields. Any message that does "MUST be treated as malformed" (section 8.2.2). This is a common cause of browser-visible ERR_HTTP2_PROTOCOL_ERROR when an origin behind a proxying edge emits HTTP/1.1-style hop headers. Cloudflare's own troubleshooting for that error points at RFC 9113 field validity (Cloudflare, HTTP/2).
  • The downgrade is quiet. Nothing fails loudly when h2 is unavailable. ALPN selection belongs to the server. So a wrong certificate, missing ALPN or an intermediary in front just settles on an older version. Cloudflare's HTTP/2 setting does nothing without a certificate at its edge. A CloudFront viewer without TLSv1.2 or SNI silently uses an earlier HTTP version. Measure the negotiated protocol. Do not assume it.
  • Enabling HTTP/2 to a fragile origin can hurt. Cloudflare warns: "If your origin does not support multiplexing, enabling HTTP/2 to origin may result in 5xx errors, particularly 520s". Cloudflare also warns that concurrency set too high for an underpowered origin can produce stream resets and 5xx spikes (Cloudflare, HTTP/2 to Origin).
  • Do not build on RFC 7540 priority signals. Many server deployments ignored them. RFC 9113 records that the RFC 7540 prioritization signaling "was not successful" (section 5.3.1). A dependency tree you send may simply be discarded.

Best practice

  • Terminate HTTP/2 on TLS 1.2 or higher with SNI: "Implementations of HTTP/2 MUST use TLS version 1.2 [TLS12] or higher for HTTP/2 over TLS", and "The TLS implementation MUST support the Server Name Indication (SNI)" extension (RFC 9113 section 9.2). An older TLS floor silently costs you h2.
  • Verify the negotiated protocol on both legs rather than assuming it. For the client leg, use curl --http2, openssl s_client -alpn h2 or the Protocol column in dev tools. For the origin leg, use your CDN's origin-protocol setting or origin logs.
  • Undo the HTTP/1.1-era workarounds. Stop sharding assets across hostnames. Stop concatenating files purely to cut request count, because HTTP/2 removes the many-connection cost that justified them (RFC 9113 section 1).
  • Set SETTINGS_MAX_CONCURRENT_STREAMS deliberately. The specification recommends no smaller than 100 (RFC 9113 section 6.5.2). Clients tend not to wait for the server's SETTINGS frame before assuming that value. When Cloudflare temporarily lowered its limit to 64 during Rapid Reset mitigation, the excess streams were reset and legitimate page loads failed (Cloudflare, technical breakdown). Advertising a stricter limit from a fragile origin is the supported way to throttle a backend.
  • Keep RST_STREAM abuse detection enabled wherever you terminate HTTP/2. Patch server implementations for CVE-2023-44487 rather than relying on concurrency limits (NVD, CVE-2023-44487).
  • If you need response ordering, signal it with the RFC 9218 Priority header field rather than reimplementing RFC 7540 dependency trees (RFC 9113 section 5.3.2).
  • Offer HTTP/3 alongside HTTP/2, not instead of it. QUIC provides "reliability at the stream level and congestion control across the entire connection" (RFC 9114 section 1.2). A client discovers an HTTP/3 endpoint "via the Alt-Svc HTTP response header field or the HTTP/2 ALTSVC frame" (RFC 9114 section 3.1.1). So h2 stays the connection that advertises h3 and the fallback when UDP is blocked.

Examples

# Check HTTP/2 support
$ curl -sI --http2 https://example.com/ | head -1
HTTP/2 200

# Nginx: enable HTTP/2
server {
    listen 443 ssl;
    http2 on;  # Nginx 1.25+
    # Or: listen 443 ssl http2;  # Older Nginx
}

# Verify with openssl
$ openssl s_client -alpn h2 -connect example.com:443 2>&1 | grep ALPN
ALPN protocol: h2

# Chrome DevTools: Protocol column shows h2 for HTTP/2 requests

Frequently Asked Questions

Version of HTTP that carries many concurrent request/response streams over one TCP connection and compresses header fields with HPACK, so clients no longer need parallel connections. HTTP semantics are unchanged and TCP head-of-line blocking remains. RFC 9113; ALPN token "h2" over TLS.

# Check HTTP/2 support
$ curl -sI --http2 https://example.com/ | head -1
HTTP/2 200

# Nginx: enable HTTP/2
server {
    listen 443 ssl;
    http2 on;  # Nginx 1.25+
    # Or: listen 443 ssl http2;  # Older Nginx
}

# Verify with openssl
$ openssl s_client -alpn h2 -connect example.com:443 2>&1 | grep ALPN
ALPN protocol: h2

# Chrome DevTools: Protocol column shows h2 for HTTP/2 requests

Yes. HTTP/2 is also known as h2. Version of HTTP that carries many concurrent request/response streams over one TCP connection and compresses header fields with HPACK, so clients no longer need parallel connections. HTTP semantics are unchanged and TCP head-of-line blocking remains. RFC 9113; ALPN token "h2" over TLS.

Related CDN concepts include: