HPACK compression is how HTTP/2 squeezes headers down to almost nothing, even when they’re identical across requests.

Let’s watch it in action. Imagine a server sending two requests back-to-back, both with the same User-Agent header.

Request 1:

GET /resource1 HTTP/2
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
Accept: */*

Request 2:

GET /resource2 HTTP/2
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
Accept: */*

Without HPACK, that User-Agent string would be sent in full twice. With HPACK, the second request might send only a few bytes. How?

HPACK uses two main mechanisms: an indexed header field representation and a literal header field with incremental indexing representation.

The first, and most powerful, is the indexed header field representation. The HTTP/2 connection maintains a shared dynamic table on both the client and server. This table stores header fields that have been recently sent. When a header field is sent that’s already in the dynamic table, instead of sending the entire header name and value, the sender just sends an index number pointing to that entry in the table.

For example, if the User-Agent header from Request 1 was added to the dynamic table, Request 2 could simply send an index representing that entry. This index is typically a small integer, often 1-4 bytes. The server looks up this index in its own dynamic table, finds the User-Agent string, and reconstructs the full header.

The second mechanism is literal header field with incremental indexing. If a header field isn’t in the dynamic table, it can be sent literally, but with instructions to add it to the table for future use. This is useful for headers that are used frequently but haven’t been seen yet on this connection. The sender transmits the header name and value, along with a flag indicating that it should be added to the dynamic table.

Consider the Host header. It’s usually the same for all requests to a given domain. The first time it’s sent, it might be sent literally. If it’s added to the dynamic table, subsequent requests can use its index.

There’s also a literal header field without indexing option. This is for headers that are unique to a request or shouldn’t be stored for future use.

The dynamic table has a maximum size, configurable by both client and server. If the table gets full, older entries are evicted. This eviction policy is crucial: typically, the least recently used (LRU) entries are removed. This ensures that actively used headers remain in the table. The SETTINGS_HEADER_TABLE_SIZE parameter in HTTP/2 is used to negotiate this size, with a default of 4096 bytes.

The core problem HPACK solves is the inefficiency of sending verbose, often repetitive, HTTP headers over the network, especially in high-traffic scenarios or with many small requests. Before HPACK, headers could consume a significant portion of the bandwidth, negating some benefits of multiplexing. By leveraging a shared, stateful compression context (the dynamic table), HPACK dramatically reduces header overhead.

The most surprising thing about HPACK’s dynamic table is that it’s not just about deduplicating identical strings. It deduplicates entire header field pairs (name and value). So, if you send User-Agent: Mozilla/5.0 ... and later User-Agent: Chrome/91.0 ..., these are distinct entries. The magic happens when the exact same pair appears again. The table stores the full name-value pair.

If you’re debugging HPACK issues, you’ll often see h2 or h2c in your network capture tools. Look at the raw bytes. A header that would be dozens or hundreds of bytes in HTTP/1.1 might be just 2 or 3 bytes in HTTP/2 after HPACK compression. For example, a common header like Accept: */* might be represented by an index like 0x52 (decimal 82), which is just one byte.

The next thing you’ll run into is how HPACK handles header table size updates and potential security implications.

Want structured learning?

Take the full Http2 course →