HTTP/2 in Practice: Why Server Push Fails on AWS
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).

Test Architectures

Our two test architectures consist of:
- A Public ALB (AWS Application Load Balancer).
- Varnish servers (version 5.2).
- An Internal ALB.
- 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.
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 !