skip to content

HTTP/2 and HTTP/3 replace the Host header with an `:authority` pseudo-header. What are pseudo-headers, what rules govern `:authority`, and how does a proxy translate between `:authority` and Host when downgrading to HTTP/1.1?

level: middleimportance: should knowfreq 33%

answer

  1. No request line in h2/h3 — pseudo-headers instead
  2. :method :scheme :path :authority, colon-prefixed, first, lowercase
  3. Host allowed but must equal :authority
  4. CONNECT: only :method + :authority
  5. Downgrade: :authority → Host, :scheme → X-Forwarded-Proto

basics

~20 s

Pseudo-headers are colon-prefixed fields (:method, :scheme, :path, :authority) that encode the request line in HTTP/2 and HTTP/3. :authority carries what Host carried. They must come before regular fields; if Host also appears it must match, and a proxy downgrading to HTTP/1.1 generates Host from :authority.

solid answer

~50 s

HTTP/2 and HTTP/3 have no request line — everything is a header field in a compressed block. The request line's parts become **pseudo-header fields**, distinguished by a leading colon: `:method`, `:scheme`, `:path`, and `:authority`. `:authority` holds the host and optional port, i.e. exactly what `Host` holds in HTTP/1.1. Rules worth knowing: - Pseudo-headers must appear **before** all regular fields in the block; a field block that interleaves them is malformed. - All field names are lowercase in HTTP/2 and HTTP/3; an uppercase name is a protocol error. - A normal request needs `:method`, `:scheme`, `:path`, and `:authority`. `CONNECT` is the exception: it sends `:method` and `:authority` only. - `Host` may still be present; if it is, it must be equal to `:authority`, otherwise the request is malformed. When an intermediary downgrades HTTP/2 to HTTP/1.1 it constructs `Host` from `:authority` and rebuilds the request line from `:method` and `:path`. Upgrading in the other direction, it sets `:authority` from Host (or from the absolute-form target).

code

http · 6 lines
http
:method: GET
:scheme: https
:authority: shop.example.com
:path: /cart
user-agent: curl/8.4.0
accept: */*

go deeper

for a junior

Know that HTTP/2 and HTTP/3 use :authority in place of Host and that it carries the same host and port.

for a middle

List the four request pseudo-headers, the ordering and lowercase rules, and the Host-must-match-:authority rule.

for a senior

Explain the translation at a version boundary, including the loss of :scheme and the X-Forwarded-Proto workaround, plus how :authority relates to coalescing and 421.

for a principal

Reason about where in the edge the authority is normalised and validated, how mixed-version hops affect logging, header budgets and smuggling risk, and what your framework actually reports as the host.

## Why the header changed shape HTTP/1.1 messages are text: a request line (`GET /cart HTTP/1.1`) followed by header fields. HTTP/2 (RFC 9113) and HTTP/3 (RFC 9114) are binary and multiplexed — many concurrent requests share one connection, each as a stream of frames — and there is no request line at all. The information the request line carried has to live somewhere, so it was folded into the header block as specially-named fields. Those fields are **pseudo-headers**: names beginning with a colon. There are four for requests: | Pseudo-header | HTTP/1.1 equivalent | |---|---| | `:method` | the verb in the request line | | `:scheme` | `http` or `https` — implicit in HTTP/1.1 | | `:path` | the path and query in the request line | | `:authority` | the `Host` header | Responses have exactly one: `:status`. ## Rules that govern the field block - **Ordering.** All pseudo-headers must precede all regular header fields. A field block where a regular field appears before a pseudo-header is treated as malformed and the stream is reset. - **Case.** Field names are lowercase. `Host:` written with a capital H in HTTP/2 is a protocol error, which is why h2 traces look like `content-type`, `user-agent`, and so on. - **Unknown pseudo-headers** are not allowed; a receiver must treat them as malformed. This keeps the set closed and predictable. - **Required set.** An ordinary request carries `:method`, `:scheme`, `:path`, and `:authority`. `:path` must not be empty for `http`/`https` requests (use `/`). - **CONNECT.** The tunnel method omits `:scheme` and `:path` and sends `:authority` as the tunnel target, e.g. `:authority: example.com:443`. That mirrors HTTP/1.1's authority-form request target. - **Coexistence with Host.** A client may still send a regular `host` field. If both are present they must carry the same value; a mismatch is malformed and must be rejected. This is the same anti-ambiguity rule that makes duplicate Host a 400 in HTTP/1.1, and for the same reason: two components resolving the authority differently is a smuggling primitive. ## Compression Header fields in HTTP/2 are compressed with HPACK, in HTTP/3 with QPACK. Both keep a static table of common field names and values plus a dynamic table populated as the connection runs. `:authority` is a static-table entry, and because the value repeats on every request over the same connection, it compresses to a couple of bytes after the first request. That is one reason header bloat is cheaper on h2/h3 than on HTTP/1.1 — though only on the hop that speaks h2. ## Translation across a version boundary Most real deployments have a version boundary: a browser speaks HTTP/2 or HTTP/3 to a CDN or load balancer, which speaks HTTP/1.1 to an origin. The intermediary must translate. **Downgrade (h2/h3 → HTTP/1.1):** ``` :method: GET → GET /cart HTTP/1.1 :path: /cart :authority: shop.example.com → Host: shop.example.com :scheme: https → (dropped; often surfaced as X-Forwarded-Proto) ``` The `:scheme` has no HTTP/1.1 home, so proxies typically communicate it out-of-band with `X-Forwarded-Proto` or `Forwarded`. Applications behind such a proxy that build absolute URLs need that value, or they will emit `http://` links from an HTTPS site. **Upgrade (HTTP/1.1 → h2/h3):** the intermediary sets `:method` and `:path` from the request line, `:authority` from Host (or from the absolute-form request target if present), and `:scheme` from how the connection was made. ## Why it matters in practice - **Server code rarely sees the difference.** Most frameworks normalise: Servlet `request.getServerName()`, Go's `r.Host`, nginx's `$http_host`/`$host` are populated from `:authority` when the connection is h2. But logging that greps literally for a `Host:` line will come up empty on an h2 hop. - **Connection coalescing** is expressed through `:authority`: a client may send requests for several authorities over one connection when the certificate covers them all. A server that is not authoritative for the `:authority` it receives should answer `421 Misdirected Request`. - **Header size limits still exist** on h2/h3, expressed as `SETTINGS_MAX_HEADER_LIST_SIZE` over the *uncompressed* size, so compression does not let you escape a header budget. - **Debugging:** `curl --http2 -v` prints the pseudo-headers; `nghttp -v` and browser devtools show them lowercase with leading colons, which surprises people the first time.

  • An HTTP/2 request arrives with both `:authority: a.example.com` and `host: b.example.com`. What should the server do?
    Treat the request as malformed and reject it — reset the stream, or respond with a 400-class error. The specification requires that when both are present they carry the same value, because two hops resolving the authority differently is exactly the ambiguity that enables request smuggling and cache poisoning. Servers should not silently prefer one.
  • HPACK compresses `:authority` to a few bytes after the first request. Does that mean header size limits stop mattering on HTTP/2?
    No. Limits on HTTP/2 are expressed by SETTINGS_MAX_HEADER_LIST_SIZE and are computed on the *uncompressed* field list, so a huge cookie still counts fully against the budget. Compression saves bandwidth on the wire, not the server's parsing and buffering budget, and the moment a proxy downgrades to HTTP/1.1 the full uncompressed bytes reappear on that hop.

saying these in an interview costs you the question

  • Saying HTTP/2 removed the concept of a host entirely rather than renaming it to :authority.
  • Writing pseudo-headers after normal headers or with capital letters — both are protocol errors.
  • Assuming :scheme survives a downgrade to HTTP/1.1; it does not, which is why X-Forwarded-Proto exists.
  • Claiming Host is forbidden in HTTP/2; it is allowed but must equal :authority.
  • Thinking header compression removes the need for header size budgets.

context