You’re seeing weird HTTP/2 behavior in your browser, and the usual suspects aren’t cutting it. The core issue is that HTTP/2’s multiplexing and header compression (HPACK) make requests and responses appear as a single, intertwined stream at the TCP level, obscuring individual request-response pairs when you only look at raw packet captures.

Common Causes and Fixes

1. TLS Handshake Failure or Misconfiguration

  • Diagnosis:
    • Browser DevTools: Open Network tab, filter by "All". Look for requests that fail with a (failed) net::ERR_SSL_PROTOCOL_ERROR or similar TLS-related error. Check the Security tab for certificate warnings.
    • Wireshark: Filter by tcp.port == 443. Look for TCP resets (TCP RST) immediately after the client hello or during the certificate exchange. You might also see malformed TLS records.
  • Causes:
    • Expired or Invalid Certificate: The server’s SSL certificate is no longer valid or doesn’t match the hostname.
    • Unsupported TLS Version/Cipher Suite: The client (browser) and server cannot agree on a secure protocol version (e.g., TLS 1.2) or a mutually supported cipher suite. This is less common with modern browsers but can happen with older servers or specific configurations.
    • Intermediate Certificate Missing: The server isn’t sending the full certificate chain, preventing the browser from validating the end-entity certificate.
    • SNI (Server Name Indication) Mismatch: If the server hosts multiple SSL certificates, it might not be correctly identifying the requested hostname during the handshake.
  • Fixes:
    • Certificate: Renew or replace the server’s SSL certificate. Ensure the hostname in the certificate matches the one accessed.
    • TLS Version/Cipher: Configure the server to support modern TLS versions (TLS 1.2, TLS 1.3) and a robust set of cipher suites. For example, in Nginx, you might set ssl_protocols TLSv1.2 TLSv1.3; and ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';.
    • Intermediate Certificates: Ensure your web server configuration includes the full certificate chain. In Nginx, this is typically done by concatenating the server certificate and intermediate certificates into a single file specified by ssl_certificate.
    • SNI: Verify that your web server is configured to handle SNI correctly, especially if you’re using multiple hostnames on the same IP address.
  • Why it works: The TLS handshake is the foundation for secure HTTP/2 communication. If this fails, the browser will refuse to establish an HTTP/2 connection, often falling back to HTTP/1.1 or reporting a connection error.

2. HTTP/2 Connection Not Negotiated (ALPN Failure)

  • Diagnosis:
    • Browser DevTools: Network tab. Requests will likely show up as using HTTP/1.1, or you’ll see (failed) net::ERR_SPDY_PROTOCOL_ERROR or net::ERR_HTTP2_PROTOCOL_ERROR if it attempted HTTP/2 and failed.
    • Wireshark: Filter by tcp.port == 443. After the TLS handshake, look for an Application-Layer Protocol Negotiation (ALPN) extension within the TLS Client Hello. You should see "h2" (for HTTP/2) listed. If it’s missing or only "http/1.1" is present, the server didn’t advertise HTTP/2 support.
  • Causes:
    • Server Not Configured for HTTP/2: The web server (Nginx, Apache, Caddy, etc.) is not enabled or configured to use HTTP/2.
    • Proxy/Firewall Interference: An intermediate network device is stripping or modifying the ALPN extension during the TLS handshake.
  • Fixes:
    • Server Config: Enable HTTP/2 in your web server’s configuration.
      • Nginx: Add http2 to the listen directive: listen 443 ssl http2;.
      • Apache: Ensure mod_http2 is enabled and add Protocols h2 http/1.1 to your virtual host configuration.
    • Proxy/Firewall: Investigate and reconfigure any network devices between the client and server to allow the ALPN extension.
  • Why it works: HTTP/2 is negotiated during the TLS handshake using the ALPN extension. If the server doesn’t offer "h2" via ALPN, the browser will default to HTTP/1.1 or report an error if it was expecting HTTP/2.

3. HPACK Header Compression Issues

  • Diagnosis:
    • Browser DevTools: Network tab. Individual requests might appear corrupted, headers might be missing or incorrect, or you might see (failed) net::ERR_SPDY_COMPRESSION_ERROR or net::ERR_HTTP2_PROTOCOL_ERROR.
    • Wireshark: Filter by tcp.port == 443. This is where it gets tricky. You’ll see interleaved TCP segments. To see HPACK, you need to decrypt the TLS traffic (if possible) and then look for HTTP/2 frames. Within those frames, you’ll see HPACK decoding. If there are errors here, Wireshark might flag them. The challenge is that HPACK errors are often subtle and manifest as malformed requests later.
  • Causes:
    • Corrupted Headers: A rare bug in the server or a client library sending malformed HPACK data.
    • Large/Complex Headers: Extremely large or numerous headers can sometimes stress HPACK implementations, though this is less common now.
  • Fixes:
    • Update Server/Client Software: Ensure your web server and any client libraries involved are running the latest stable versions, as HPACK bugs are often patched.
    • Simplify Headers: If possible, reduce the size and complexity of your request/response headers.
  • Why it works: HPACK is designed to compress HTTP headers efficiently. If the compression or decompression process fails, it leads to corrupted data, making requests unprocessable.

4. Stream Multiplexing Errors

  • Diagnosis:
    • Browser DevTools: Network tab. You might see requests that seem stuck, requests that complete out of order, or the browser might hang entirely. Errors like net::ERR_HTTP2_PROTOCOL_ERROR are common.
    • Wireshark: Filter by tcp.port == 443. You’ll see many HTTP/2 frames (SETTINGS, WINDOW_UPDATE, DATA, HEADERS, RST_STREAM) interleaved on the same TCP connection. Identifying a specific request’s frames can be difficult. Look for RST_STREAM frames, which indicate a specific HTTP/2 stream was abruptly closed, often due to an error on either the client or server side.
  • Causes:
    • Server Resource Exhaustion: The server might be overwhelmed, unable to manage the numerous concurrent streams, leading to dropped connections or stream resets.
    • Client Resource Limits: Less common, but a browser might hit its own limits for concurrent streams.
    • Network Congestion/Packet Loss: High packet loss can disrupt the ordered delivery required for stream management, leading to timeouts or stream resets.
  • Fixes:
    • Server Tuning: Adjust HTTP/2-specific settings on your server. For example, Nginx has directives like http2_max_concurrent_streams (default is 256) and http2_max_requests (default is 1000). Increase these if you suspect resource exhaustion, but monitor server load.
    • Network Quality: Address underlying network issues if packet loss is high.
    • Client-Side: Ensure your browser is up-to-date.
  • Why it works: HTTP/2’s power comes from multiplexing many requests/responses over a single TCP connection. If the system managing these streams (either client or server) fails to keep them ordered, acknowledge updates, or handle errors gracefully, streams can be reset, or the entire connection can break.

5. Server-Side Push Issues

  • Diagnosis:
    • Browser DevTools: Network tab. You might see resources loaded via "Push" that are unexpectedly missing, or the page rendering might be broken because pushed assets weren’t received or processed correctly.
    • Wireshark: Filter by tcp.port == 443. Look for PUSH_PROMISE frames followed by HEADERS and DATA frames for the pushed resource. If a PUSH_PROMISE is sent but the corresponding DATA frames are never received, or if a RST_STREAM occurs for the pushed stream, it indicates a problem.
  • Causes:
    • Server Configuration: Server-side push is not enabled or misconfigured.
    • Client Rejection: The browser might reject a pushed resource for various reasons (e.g., it already has it cached, or it’s deemed unnecessary). This can sometimes lead to confusing error states if not handled cleanly.
    • Intermediary Interference: Proxies or CDNs might strip PUSH_PROMISE frames.
  • Fixes:
    • Enable/Configure Push: Ensure server-side push is explicitly enabled and configured correctly in your web server. For Nginx, this involves http2_push_preload on; and add_header Link "</path/to/resource.css>; rel=preload; as=style";.
    • Client Behavior: Understand that modern browsers are increasingly disabling or limiting server-side push due to its complexity and potential for misuse. You might need to rely on standard preloading (<link rel="preload">) instead.
  • Why it works: Server-side push allows the server to proactively send resources it anticipates the client will need. If this mechanism is broken, either the promised resources aren’t sent, or they aren’t processed correctly by the client or intermediaries.

After fixing these, you’ll likely run into issues with HTTP/2’s flow control, specifically managing the WINDOW_UPDATE frames to ensure efficient data transfer without overwhelming buffers.

Want structured learning?

Take the full Http2 course →