LEARN · DEBUGGING GUIDE

Debugging HPACK Errors in HTTP/2 Header Compression

HPACK errors manifest as stream resets or connection failures with cryptic error codes like COMPRESSION_ERROR or PROTOCOL_ERROR. This guide cuts through the noise with specific commands, log patterns, and real-world fixes.

AdvancedHTTP / Networking9 min read

What this usually means

HPACK errors indicate that the HTTP/2 header compression process broke down between client and server. Common root causes include a server-side HPACK implementation that violates the RFC (e.g., invalid dynamic table size updates, incorrect index references), oversized header fields that exceed the configured max header list size, or a mismatch in the decoder context (e.g., after a GOAWAY with graceful shutdown, the dynamic table state is inconsistent). These errors are notoriously hard to reproduce because they depend on exact header sequences and compression states. Unlike HTTP/1.1 where a malformed header is obvious, in HTTP/2 the compression layer can silently corrupt data before the error surfaces as a stream reset.

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Enable verbose HTTP/2 logging in your server (e.g., for nginx: 'error_log /var/log/nginx/error.log debug;' and check for 'hpack' or 'hpack' strings)
  • 2Capture traffic with Wireshark and apply display filter 'http2' — look for 'Malformed Packet' or frames with 'Error: COMPRESSION_ERROR' in the expert info
  • 3Reproduce with curl --http2 -v and decode the raw frames using 'nghttp2 -v' to see the exact HEADERS payload
  • 4Check server configuration for 'http2_max_header_size' or 'http2_max_field_size' (e.g., in nginx 'large_client_header_buffers' has no effect on HTTP/2)
  • 5Inspect the response headers that trigger the error: if a header value exceeds 4KB (the default max dynamic table size), it may be the culprit
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • search/var/log/nginx/error.log (with debug level) — look for 'hpack' or 'hpack' substring
  • searchWireshark capture with display filter 'http2.headers' — examine the HEADERS frame hex dump for invalid Huffman encoding
  • searchnghttp2 (nghttp) output with -v flag — shows decoded HPACK entries and any decompression errors
  • searchServer configuration files: nginx.conf (http2_max_field_size), Apache httpd.conf (H2MaxFieldSize), or your application server settings
  • searchApplication-level access logs with %{http2} variable to correlate error timing with specific requests
  • searchGo net/http/pprof or Java flight recorder for thread dumps if HPACK decompression is on the client side
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningOversized header values (e.g., Set-Cookie with large session tokens) exceeding the server's max header field size (default 4KB in many servers)
  • warningServer HPACK implementation bug: dynamic table size update not respecting SETTINGS_HEADER_TABLE_SIZE from client
  • warningMisconfigured reverse proxy that mangles HTTP/2 to HTTP/1.1 conversion, causing headers to be re-encoded incorrectly
  • warningClient or server using a stale or corrupt compression context after a GOAWAY frame that was not fully processed
  • warningThird-party middleware (e.g., WAF, load balancer) that modifies headers on the fly without understanding HPACK
  • warningHuffman encoding errors: custom header encoding routines that produce invalid Huffman codes
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildIncrease the max header field size on the server: for nginx, add 'http2_max_field_size 16k;' in the http block
  • buildReduce header sizes by consolidating cookies, moving data to payload, or using shorter header names
  • buildUpdate or patch the HPACK library (e.g., nginx's ngx_http_v2_module, or the HTTP/2 library in your language)
  • buildDisable dynamic table compression if static table only is acceptable (e.g., set SETTINGS_HEADER_TABLE_SIZE to 0 in client)
  • buildConfigure reverse proxies to pass through HTTP/2 without downgrading to HTTP/1.1 (e.g., use HTTP/2 between proxies as well)
  • buildValidate third-party middleware that modifies headers (e.g., Akamai, Cloudflare) — check if they support HPACK correctly
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedSend a request with curl --http2 -v and check that the response completes without HTTP2 framing errors
  • verifiedUse nghttp2's 'nghttp' client to send the exact same request and confirm no decompression errors in verbose output
  • verifiedMonitor server error logs for HPACK-related messages after applying the fix; they should disappear
  • verifiedRun a load test with large headers (e.g., 10KB header values) to ensure the new limits hold
  • verifiedCapture a Wireshark trace and verify that HEADERS frames are no longer marked as malformed
  • verifiedCheck the server's HPACK dynamic table size via a debugging endpoint (if available) to confirm it matches the SETTINGS
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningDo not blindly increase max header sizes without considering memory and performance impact (each connection has its own HPACK table)
  • warningDo not assume the error is client-side just because the RST_STREAM comes from the server — it's often the server rejecting its own compression output
  • warningDo not ignore the role of intermediaries: a CDN or reverse proxy can introduce HPACK errors even if origin and client are fine
  • warningDo not patch HPACK bugs with workarounds like disabling HTTP/2 entirely — that loses performance and masks the real issue
  • warningDo not use outdated tools like curl < 7.51 which had HPACK bugs themselves
  • warningDo not skip verifying the fix with actual production traffic patterns (e.g., authenticated sessions with large cookies)
( 07 )War story

The 8KB Cookie That Broke HTTP/2

Senior Backend Engineernginx 1.18.0, Go 1.14 HTTP/2 client, AWS ALB

Timeline

  1. 09:15User reports intermittent 'ERR_SPDY_PROTOCOL_ERROR' in Chrome when accessing dashboard
  2. 09:30I check nginx error logs: nothing at info level; enable debug logging
  3. 09:45Reproduce with curl --http2 -v; get 'curl: (16) Error in the HTTP2 framing layer' after login
  4. 10:00Capture with Wireshark; see HEADERS frame followed by RST_STREAM with COMPRESSION_ERROR
  5. 10:15Decode the HEADERS frame with nghttp2; notice a Set-Cookie header of 8192 bytes
  6. 10:30Check nginx default http2_max_field_size: 4k (4096 bytes). That's the limit.
  7. 10:45Add 'http2_max_field_size 16k;' to nginx.conf and reload
  8. 11:00Test again; error disappears. Monitor logs for the next hour — no HPACK errors.
  9. 11:30Root cause confirmed: oversized cookie exceeded default HPACK field size limit.

The ticket came in as 'intermittent ERR_SPDY_PROTOCOL_ERROR in Chrome'. The dashboard worked fine in Firefox, which made me suspect HTTP/2 implementation differences. I quickly reproduced with curl --http2 -v on the server and saw 'curl: (16) Error in the HTTP2 framing layer'. That was my cue to go deeper.

I ran a Wireshark capture from the client side — the server was sending a RST_STREAM with COMPRESSION_ERROR right after the first HEADERS frame from the server. Using nghttp2 -v, I decoded the HEADERS frame and found a Set-Cookie header with 8192 bytes of session data. Our app was storing a large JSON blob in a cookie.

Nginx's default http2_max_field_size is 4KB. When the server tried to encode that 8KB header into HPACK, it exceeded the limit and the server (or the client's HPACK decoder) threw COMPRESSION_ERROR. I added 'http2_max_field_size 16k;' to the http block, reloaded, and the error vanished. The lesson: always check default limits when introducing HTTP/2, and watch out for oversized cookies.

Root cause

Server-side HPACK field size limit (4KB default in nginx) was exceeded by a 8KB Set-Cookie header, causing the server to reject its own compressed header with COMPRESSION_ERROR.

The fix

Added 'http2_max_field_size 16k;' to nginx.conf http block and reloaded configuration.

The lesson

HTTP/2 has strict limits on header sizes; always verify defaults for your reverse proxy and application. Intermittent errors often point to headers that vary per request.

( 08 )HPACK Dynamic Table State Mismanagement

HPACK uses a dynamic table shared between client and server for each connection. The table size is negotiated via SETTINGS_HEADER_TABLE_SIZE, defaulting to 4096 bytes. A common bug is when the server sends a dynamic table size update that exceeds the client's advertised limit, causing the client to reject the update with COMPRESSION_ERROR. Conversely, a client may send a size update that the server doesn't expect, especially after a GOAWAY with graceful shutdown where the dynamic table state is reset.

To diagnose, enable HPACK trace logging in your server (e.g., nginx with debug log and grep for 'hpack'). Look for 'invalid table size update' or 'new table size too large'. The fix typically involves ensuring both sides respect the SETTINGS_HEADER_TABLE_SIZE limit and that the table size is only updated within the bounds of the peer's setting. For nginx, the relevant configuration is http2_max_field_size but also check if you have any third-party modules manipulating the dynamic table.

( 09 )Huffman Encoding Errors in Custom Implementations

HPACK allows either plain string literals or Huffman-encoded strings. A Huffman encoding error occurs when the encoded bitstream does not match the HPACK Huffman code table (e.g., using a non-terminated code or an invalid padding). This results in immediate decompression failure. This is rare in mature libraries but can happen in custom proxy code or WAFs that attempt to modify headers on the fly without properly re-encoding.

If you suspect Huffman errors, use nghttp2's 'nghttp' with -v to decode the headers. It will show 'HPACK: invalid Huffman code' with the exact byte offset. Check any middleware that modifies headers (e.g., adding security headers, rewriting cookies). The fix is to ensure the middleware uses a proper HPACK encoder/decoder library. Avoid manual bit manipulation.

( 10 )Intermediary-Induced HPACK Corruption

When HTTP/2 is terminated at a reverse proxy or load balancer, and then forwarded as HTTP/1.1 to the backend, the headers are decompressed from HPACK and re-encoded. If the proxy then forwards the response back as HTTP/2, it compresses the headers again. Any bug in the proxy's HPACK implementation (e.g., incorrect handling of connection-specific headers like Transfer-Encoding) can introduce corruption. Amazon ALB and nginx have had such bugs in the past.

To diagnose, compare the headers at each hop using tracing headers (e.g., X-Request-ID) and capture traffic on both sides of the proxy. If the error appears only when the proxy is involved, the proxy is likely at fault. The fix may involve upgrading the proxy software or bypassing it for HTTP/2 by using direct connections. In some cases, you can disable HPACK dynamic table usage (by setting SETTINGS_HEADER_TABLE_SIZE to 0) on the proxy-to-client connection as a workaround.

( 11 )Oversized Headers and Default Limits

Many servers impose a default limit on the size of a single header field or the entire header block. For example, nginx's http2_max_field_size defaults to 4KB, and http2_max_header_size defaults to 16KB. If a header field exceeds this limit, the server may reject the request or response with a COMPRESSION_ERROR or even a 431 Request Header Fields Too Large. However, the error manifestation is different: in HTTP/2, the server may simply reset the stream with COMPRESSION_ERROR instead of sending a 431.

To identify oversized headers, look for patterns: the error occurs only when certain cookies, JWTs, or authorization tokens are present. Use curl with --header to send large headers and see if it triggers the error. The fix is to increase the limits (http2_max_field_size and http2_max_header_size) or reduce header sizes in the application. Be aware that increasing limits consumes more memory per connection for the HPACK table.

( 12 )Debugging with nghttp2 and Wireshark: A Step-by-Step

Start by installing nghttp2 (apt install nghttp2) and Wireshark. Capture the HTTP/2 traffic with tcpdump: 'sudo tcpdump -i eth0 -w hpack_error.pcap port 443'. Open the pcap in Wireshark, apply filter 'http2', and look for RST_STREAM frames with error codes. Right-click on the RST_STREAM and select 'Follow TCP Stream' to see the sequence. The expert info (Analyze -> Expert Info) will highlight malformed packets.

Next, reproduce with nghttp2: 'nghttp -v https://your-server.com/path' — this will decode and print each HPACK entry. If you see an error like 'HPACK: decompression failed', the output will indicate the exact header and the reason. For deeper analysis, use 'nghttp -v --no-dep' to avoid dependency on the dynamic table and see raw indexes. Compare with a working client like Firefox to isolate the problem to a specific header set.

Frequently asked questions

What does COMPRESSION_ERROR mean in HTTP/2?

COMPRESSION_ERROR (error code 0x9) indicates a failure in HPACK decompression. It means the recipient (client or server) received a HEADERS or CONTINUATION frame that could not be decoded according to the HPACK specification. This can be due to invalid Huffman encoding, a dynamic table size update that exceeds the agreed limit, an out-of-bounds index reference, or a malformed integer encoding. It is a fatal connection error that resets the stream.

How do I increase the HPACK table size in nginx?

Nginx does not directly expose a setting to change the HPACK table size. The table size is negotiated via SETTINGS_HEADER_TABLE_SIZE, which is typically the client's setting. However, nginx has 'http2_max_field_size' (default 4KB) and 'http2_max_header_size' (default 16KB) that limit individual header field and total header block sizes. To effectively allow larger headers, increase these values. For the dynamic table size, nginx uses a fixed table of 4096 bytes as per the spec default.

Can a browser extension cause HPACK errors?

Yes, browser extensions that modify HTTP headers (e.g., ad blockers, privacy tools) can cause HPACK errors if they inject large or malformed headers. Since the extension runs at the HTTP/1.1 level, the browser's HTTP/2 stack may re-encode these headers into HPACK, potentially exceeding limits or creating invalid Huffman codes. To test, disable all extensions and reproduce the error. If it goes away, the extension is the culprit.

Why does the error only happen intermittently?

Intermittent HPACK errors often occur because the dynamic table changes state as headers are compressed. The first request may succeed because the table is empty, but subsequent requests with similar headers may cause index references that become invalid after a GOAWAY or SETTINGS update. Also, header sizes vary per user (e.g., session cookies), so only users with large cookies trigger the limit. Look for patterns: error occurs after certain actions (login, page navigation) that produce large headers.

How do I decode an HPACK header block manually?

Use nghttp2's 'nghttp' tool with -v flag to decode headers. For raw hex, capture with Wireshark and export the HEADERS frame payload. Then use a command-line tool like 'hpack' (from the http2-spec repo) or write a script using Python's hpack library. For example: capture hex string, then in Python: from hpack import Decoder, Encoder; decoder = Decoder(); decoder.decode(hex_data). This will show the decoded header list.