HTTP/2 in Practice: Why Server Push Fails on AWS

Nov 29, 2018·
Benjamin Rabiller
Benjamin Rabiller
· 3 min read
Image credit: thehackernews.com

Introduction to HTTP/2

The HTTP/2 (h2/h2c) protocol is now a standard for web performance. Its promises are significant:

  • HPACK: Header compression to reduce overhead.
  • Server Push: Anticipated resource delivery (preload).
  • Server Hints: Assistance for browser prefetching.
  • Binary Format: More efficient than text-based formats.
  • Multiplexing: A single TCP connection to handle multiple concurrent requests.

So, why write this article?

A client informed me of their inability to get the PUSH feature working on their AWS-hosted infrastructure. I wanted to dig deeper into the subject to understand where the flow breaks in a multi-tier architecture.


Testing the PUSH Feature

Definition

HTTP/2 Server Push allows the server to send resources to the client (browser) without the latter explicitly requesting them. This saves precious round trips (RTT).

HTTP/2 Server Push mechanism diagram

Test Architectures

AWS infrastructure architecture with ALB, Varnish, and Nginx

Our two test architectures consist of:

  1. A Public ALB (AWS Application Load Balancer).
  2. Varnish servers (version 5.2).
  3. An Internal ALB.
  4. Nginx front-ends (Symfony 4 App).

The difference lies in whether HTTPS is enabled on the internal ALB and Nginx backends. For the test, I followed Kévin Dunglas’ tutorial to implement push under Symfony 4:

<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="UTF-8"> 
    <title>Welcome!</title> 
    <link rel="stylesheet" href="{{ preload(asset('main.css'), { as: 'style' }) }}"> 
</head> 
<body> 
    <main role="main" class="container"> 
        <h1>Hello World</h1> 
    </main> 
</body> 
</html> 

Diagnostic Phase: Traffic Analysis

1. Test via Public ALB (Architectures A and B)

nghttp -ans https://symfony-sample.xxx.net/

id  responseEnd requestStart  process code size request path
 13    +63.90ms        +76us  63.82ms  200   8K /
 15    +69.81ms     +63.91ms   5.90ms  200   91 /main.css

The main.css asset is not “pushed”. The absence of the asterisk (*) in nghttp confirms the failure.

2. Direct Test on Nginx

  • Architecture A (HTTP/80): Failure. Nginx does not handle HTTP/2 over TCP (h2c).
  • Architecture B (HTTPS/443):
[root@ip-10-134-168-146 ~]# nghttp -ans https://127.0.0.1:32771/ -v 
[...]
[  0.026] recv PUSH_PROMISE frame <length=52, flags=0x04, stream_id=13> 
          ; END_HEADERS 
          (padlen=0, promised_stream_id=2) 
[...]
id  responseEnd requestStart  process code size request path 
  13    +23.49ms       +727us  22.76ms  200   8K / 
   2    +26.76ms * +23.13ms   3.63ms  200   91 /main.css 

Result: OK. When connected directly via HTTPS, Nginx pushes the asset correctly.

3. Test via Internal ALB (Architecture B)

id  responseEnd requestStart  process code size request path 
  13    +28.56ms       +179us  28.38ms  200   8K / 
  15    +32.55ms     +28.58ms   3.97ms  200   91 /main.css 

Result: KO. Multiplexing is active, but the PUSH has vanished.

4. Test via Varnish

Varnish 5.0 theoretically supports h2c. Let’s check RFC 7540 Section 3.2 regarding the Upgrade mechanism.

After diagnosis, the option is not active by default:

varnishadm param.show feature 
# Value is: none (default)
# Activation:
varnishadm param.set feature +http2

Once enabled, curl confirms the switch to HTTP/2:

curl -k -svo /dev/null --http2 -H "Host: symfony-sample.xxx.net" http://127.0.0.1/httpprobe 
< HTTP/1.1 101 Switching Protocols 
< Connection: Upgrade 
< Upgrade: h2c 
* Received 101 
* Using HTTP2, server supports multi-use 

Result: KO. Still no PUSH through Varnish.


Technical Verdict: Why Server Push Fails

The inability to make PUSH work is due to protocol degradation between infrastructure layers.

AWS ALB

Amazon’s Application Load Balancer supports HTTP/2 only on the client side.

“The load balancer converts these to individual HTTP/1.1 requests […] You can’t use the server-push feature of HTTP/2.”AWS Documentation

Nginx

Nginx does not support HTTP/2 for upstream communication (proxy_pass).

“At the moment, we only support HTTP/2 on the client side. […] there’s not much benefit in HTTP/2 for low‑latency networks such as upstream connections.” — Source Nginx

Varnish

While it accepts H2, it does not natively manage the transmission of PUSH_PROMISE frames coming from the backend to redistribute them to the end client.

Conclusion

In a modern architecture (ALB -> Varnish -> Nginx), HTTP/2 Server Push is unusable. The protocol is downgraded to HTTP/1.1 at the very first internal hop. To benefit from HTTP/2 advantages, you must focus on the layer closest to the client or use standard Resource Preloading via Link headers, which remains compatible even after protocol degradation.

Benjamin Rabiller
Authors
DevOps/Cloud Architect

Actuellement ingénieur DevOps/Architecte Cloud, j’étais initialement interessé par l’administration système et grâce aux entreprises dans lesquelles j’ai pu travailler Oxalide et maintenant Claranet j’ai eu la chance de découvrir l’univers du Cloud et de l’automatisation.

Je me suis décidé a publier ce blog pour vous faire partager ma passion mais également pour enrichir avec modestie tout ce que l’on peut trouver sur internet. Bonne lecture !