I want to start this one with an important, honest caveat that a lot of tutorials on this topic skip entirely: HTTP/2 Server Push is deprecated. Chrome removed support for it back in 2022, and most other major browsers have followed suit or never fully supported it in the first place. If you’re building something new today, I would not recommend relying on Server Push as your caching or resource-prioritization strategy. That said, plenty of people land on this topic because they’re maintaining older systems, studying for certifications, working with clients on legacy infrastructure, or just genuinely curious how it worked and why it fell out of favor. So in this article, I’ll cover exactly how to configure it in Nginx, how to verify it’s working, and — just as importantly — what to use instead, since that’s genuinely the more useful takeaway for most people reading this in 2026.
What Server Push Was Trying to Solve
HTTP/2 Server Push was designed to address a specific inefficiency in how browsers load pages. Normally, a browser requests an HTML document, parses it, discovers it needs a CSS file and some JavaScript, and only then requests those additional resources. That’s at least one extra round trip before the browser even knows what else it needs.
Server Push let the server proactively send resources the browser hadn’t asked for yet, based on the server’s own knowledge that, say, every request for /index.html was going to need style.css and app.js right after. In theory, this eliminated that extra round trip and sped up page loads.
Why It Got Deprecated
In practice, Server Push caused more problems than it solved for most real-world sites:
- Over-pushing. Servers would push resources the browser already had cached, wasting bandwidth on data the client didn’t need.
- Poor prioritization. Pushed resources could compete with genuinely critical resources for bandwidth and connection priority, sometimes making page loads slower rather than faster.
- Complex cache interaction. The browser’s cache and the push mechanism didn’t coordinate well, leading to redundant pushes.
- Better alternatives emerged. The
Link: rel=preloadheader and103 Early Hintsresponse accomplish similar goals — telling the browser about resources it’ll need soon — without the downsides of forcibly pushing data the browser might not want.
Chrome’s engineering team published data showing Server Push provided negligible or even negative performance impact in most real-world scenarios, which led to its removal from Chrome and Chromium-based browsers. Since Chrome represents a large majority of global browser usage, Server Push effectively stopped mattering for the general public even on servers that still support it.
Requirements (If You Still Want to Configure It)
- Nginx built with HTTP/2 support, which requires Nginx 1.13.9 or later for the push functionality specifically (
ngx_http_v2_modulewith push directives) - A valid TLS certificate, since HTTP/2 in virtually all real-world deployments requires HTTPS (browsers only support HTTP/2 over TLS in practice, even though the spec technically allows cleartext HTTP/2)
- Awareness that only legacy or non-Chromium browsers (some older Firefox and Safari versions, though even these have deprecated or removed support over time) will actually act on the pushed resources
Check your Nginx version and build flags:
nginx -V 2>&1 | grep -o "nginx/[0-9.]*"
nginx -V 2>&1 | grep http_v2_module
Step 1: Enable HTTP/2
Server Push requires HTTP/2 as a prerequisite. In your server block:
server {
listen 443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com;
index index.html;
}
Note: in older Nginx versions (pre-1.25.1), the syntax was listen 443 ssl http2; combined on the listen line. Newer versions use the separate http2 on; directive shown above. Check your installed version’s documentation if you get a syntax warning.
Step 2: Add the http2_push Directive
This is where you tell Nginx which resources to proactively push alongside a given response:
location = /index.html {
http2_push /css/style.css;
http2_push /js/app.js;
http2_push /images/logo.png;
}
Every time a client requests /index.html, Nginx will also push these three additional resources without the client having to ask for them.
Step 3: A More Maintainable Approach Using http2_push_preload
Manually listing every pushed resource per location, as above, becomes a maintenance burden fast, especially on larger sites. Nginx offers a more elegant mechanism: http2_push_preload, which automatically pushes any resource referenced by a Link: rel=preload header your application already sets.
server {
listen 443 ssl;
http2 on;
server_name example.com;
http2_push_preload on;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
With this enabled, if your application (or a separate add_header directive) sets:
Link: </css/style.css>; rel=preload; as=style
Nginx will automatically push /css/style.css alongside the response, without you having to hardcode http2_push directives per location. This also means the same header serves double duty: browsers that don’t support push (which, again, is most of them today) will still benefit from the preload hint, fetching the resource with high priority as soon as they see the header, even without an actual push.
You can set this header directly in Nginx if you’re serving static content:
location = /index.html {
add_header Link "</css/style.css>; rel=preload; as=style, </js/app.js>; rel=preload; as=script";
}
Step 4: Test and Reload
sudo nginx -t
sudo systemctl reload nginx
Step 5: Verify Server Push Is Actually Happening
This is the tricky part, because as I mentioned, most modern tooling either doesn’t support Server Push anymore or doesn’t clearly surface it in a way you’d expect.
Using nghttp (a command-line HTTP/2 client):
sudo apt install nghttp2-client -y
nghttp -nsv https://example.com/index.html
Look for PUSH_PROMISE frames in the output — these indicate the server offered to push a resource before the client asked for it.
Using curl with HTTP/2 support:
curl --http2 -v https://example.com/index.html 2>&1 | grep -i push
Support for detecting push via curl is limited and version-dependent, so nghttp is generally more reliable for this specific verification.
Browser developer tools: Chrome removed the ability to observe Server Push behavior along with removing support for it entirely, so you won’t see push activity there anymore. Firefox’s network panel historically showed a “Push” indicator for pushed resources in versions that still supported it, but you’d need to check current behavior for whatever version you’re testing against, since this has been a moving target.
What to Use Instead
Given the deprecation, here’s what I actually recommend for the underlying performance goal Server Push was trying to achieve:
1. Link: rel=preload Headers (Without Push)
Even without pushing, a preload hint tells the browser to start fetching a critical resource with high priority as soon as it parses the response headers, well before it would otherwise discover the resource by parsing the HTML body:
location = /index.html {
add_header Link "</css/critical.css>; rel=preload; as=style" always;
}
This achieves much of the original goal — reducing the discovery delay for critical resources — without the downsides of forced pushing.
2. 103 Early Hints
This is the more modern, purpose-built replacement. The server sends a 103 Early Hints informational response containing preload links before the final response is ready, letting the browser start fetching those resources while the server is still generating the main HTML (useful when your backend takes some time to render a response). Nginx has supported Early Hints since version 1.25.1 via the add_header directive combined with the 103 status handling in newer releases, though support and exact configuration syntax has evolved, so check your installed version’s release notes and documentation for the current directive names if you want to implement this.
3. HTTP/2 (or HTTP/3) Multiplexing Itself
A large part of the original problem Server Push addressed — the overhead of multiple sequential connections — is already substantially mitigated just by using HTTP/2 or HTTP/3’s native multiplexing, which lets many resources download concurrently over a single connection without the extra round-trip penalty that plagued HTTP/1.1.
4. Resource Bundling and Inlining for Critical Assets
For genuinely critical, small resources (like above-the-fold CSS), inlining them directly into the HTML response remains one of the most reliable ways to eliminate an extra round trip entirely, sidestepping the whole push-versus-preload debate for that specific resource.
Troubleshooting Common Issues
Problem: http2_push directive causes an “unknown directive” error.
Your Nginx build doesn’t include HTTP/2 push support, or you’re running a very old or very new version where the directive syntax changed. Check nginx -V output and your version’s changelog.
Problem: Pushed resources don’t seem to have any effect on load time.
This is expected in most modern browsers, including Chrome, Edge, and other Chromium-based browsers, which simply ignore push frames now. Test with nghttp to confirm the server-side behavior is correct, but don’t expect to see a real-world performance difference for the majority of your actual visitors.
Problem: Over-pushing is slowing things down.
If you’re pushing large resources on every request regardless of whether the client already has them cached, you’re wasting bandwidth. This was one of the core reasons push fell out of favor. If you’re maintaining a legacy system, consider scaling back to rel=preload hints instead.
Problem: http2_push_preload doesn’t seem to trigger any pushes.
Confirm your Link headers are formatted correctly and that http2_push_preload on; is actually in scope for the relevant server or location block. Also confirm you’re testing with a client that both supports HTTP/2 and hasn’t deprecated push handling (again, nghttp is your most reliable testing tool here).
Security Considerations
- Be cautious about pushing resources based on any client-controllable input; pushing arbitrary content increases your attack surface for cache poisoning or resource injection if not carefully scoped.
- Server Push, like any HTTP/2 feature, requires a properly configured TLS setup; don’t cut corners on certificate management just because you’re focused on the push configuration.
- Excessive or careless pushing could theoretically be used as a resource exhaustion vector against your own server (pushing large files on every request adds server-side load and bandwidth cost), so scope your
http2_pushdirectives narrowly and deliberately.
Performance Tips
- If you’re maintaining a legacy system that still benefits meaningfully from push (a shrinking use case, but not zero), keep the pushed resource list small and focused only on truly critical, render-blocking assets.
- Prefer
http2_push_preloadcombined withLinkheaders over hardcodedhttp2_pushdirectives, since it gives you a graceful fallback (preload hint) for browsers that ignore push. - Measure, don’t assume. Use real user monitoring or synthetic testing (WebPageTest, Lighthouse) to check whether your specific push configuration is actually helping or hurting load times for your real audience’s actual browser mix.
- Seriously consider migrating any push-dependent optimization strategy to
103 Early Hintsor plainrel=preload, since these are actively supported going forward and won’t leave you maintaining dead code.
Real-World Use Cases
Given the deprecation, genuine real-world use cases for Server Push today are narrow:
- Legacy internal tooling running on older, controlled browser environments where push is still honored (e.g., an internal corporate app locked to an older browser version for compatibility reasons).
- Educational and certification contexts where understanding HTTP/2’s full feature set, including push, is part of the curriculum or exam material.
- Historical/legacy system maintenance where a previous team implemented push-based optimization and it hasn’t yet been migrated to modern alternatives.
For anything new, I’d steer you toward rel=preload and 103 Early Hints instead.
Best Practices Summary
- Understand that Server Push is deprecated in the browser landscape that matters most (Chrome and Chromium-based browsers represent the majority of traffic for most sites).
- If maintaining a legacy system, prefer
http2_push_preloadcombined withLinkheaders over manually listinghttp2_pushdirectives, for the graceful degradation it provides. - Test actual push behavior with
nghttp, not browser dev tools, since most current browsers won’t surface it. - Migrate performance optimization strategies toward
103 Early Hintsandrel=preloadfor new projects. - Don’t over-push; keep any legacy push configuration narrowly scoped to genuinely critical resources.
A Practical Migration Path Away From Push
If you’re staring at an existing Server Push configuration and wondering how to move off it responsibly, here’s the sequence I’d follow:
- Audit what’s currently being pushed. Go through every
http2_pushandhttp2_push_preloaddirective in your config and list out exactly which resources are being proactively sent. - Convert each push into a
Link: rel=preloadheader without the actual push behavior, keeping the resource prioritization hint but dropping the forced-send mechanism:add_header Link "</css/style.css>; rel=preload; as=style" always; - Measure before and after using a real-user monitoring tool or Lighthouse/WebPageTest against representative pages, comparing Largest Contentful Paint and Time to First Byte metrics across both configurations.
- Evaluate
103 Early Hintsif your backend has meaningful response generation latency (server-side rendering, database-heavy pages) where sending hints before the final response is ready would provide a genuine head start to the browser. - Remove the old
http2_pushdirectives entirely once you’ve confirmed the preload-only approach performs at least as well, which in my experience it very often does, given that most browsers ignore push anyway.
Frequently Asked Questions
Is HTTP/2 itself deprecated, or just the push feature?
Just push. HTTP/2 as a whole remains widely supported and is genuinely beneficial for its multiplexing, header compression, and other features. Don’t confuse the deprecation of one specific feature (push) with the protocol as a whole being obsolete — HTTP/2, and increasingly HTTP/3, remain very much current and worth using.
Does Nginx support HTTP/3 and does it have its own push mechanism?
Nginx has added HTTP/3 (QUIC) support in recent versions, but HTTP/3 does not include a Server Push mechanism at all — it was dropped from the protocol design given the same real-world issues that led browsers to deprecate HTTP/2’s version. This is a strong signal about where the industry consensus has landed on proactive server push as a technique.
If Chrome doesn’t support push, why does my nghttp test still show PUSH_PROMISE frames?
nghttp is a low-level protocol testing tool that implements the HTTP/2 spec faithfully, including push, regardless of what mainstream browsers choose to do with it. Seeing PUSH_PROMISE frames in nghttp output confirms your server-side configuration is technically correct, but it doesn’t tell you anything about what a real Chrome or Edge user’s browser will actually do with that server behavior (which is: ignore it).
Will enabling http2_push_preload break anything for browsers that don’t support push?
No. Browsers that don’t act on the push simply ignore the PUSH_PROMISE frames and, if you’re also sending the corresponding Link: rel=preload header (which http2_push_preload is designed to work alongside), they’ll still benefit from the preload hint through normal means. This is precisely why I recommend this combined approach if you’re maintaining a legacy configuration rather than removing preload hints entirely.
Should I bother implementing push for a brand-new project in 2026?
No, I wouldn’t. Put that implementation effort into 103 Early Hints, rel=preload hints, and general HTTP/2 or HTTP/3 adoption instead, all of which deliver real, current benefit without the baggage of a deprecated feature that the majority of your visitors’ browsers will simply disregard.
Wrapping Up
I’ll be direct about this one: if you came here to speed up a new website, Server Push isn’t the tool for that job anymore, and I’d feel like I did you a disservice if I didn’t say so clearly. But if you’re maintaining an older system, studying HTTP/2 internals, or just curious how it worked, the configuration itself is straightforward — http2 on;, http2_push or http2_push_preload, and a Link header if you want the more maintainable approach. The bigger lesson here, honestly, is a good reminder for any performance optimization: verify your techniques are still delivering value in the current browser landscape, because what worked five years ago doesn’t always hold up today.