HTTP/2 Server Push is a feature that lets the server send resources to the client before the client even asks for them.

Here’s a web server (Nginx) configured to push style.css and app.js when a client requests the main HTML page, index.html:

server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    location = /index.html {
        http2_push /style.css;
        http2_push /app.js;
        try_files $uri $uri/ =404;
    }

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.css$ {
        add_header Content-Type text/css;
    }

    location ~ \.js$ {
        add_header Content-Type application/javascript;
    }
}

When a browser requests /index.html, Nginx doesn’t just send back the HTML. It also sends style.css and app.js in parallel, using separate HTTP/2 streams. The browser receives these resources and can start parsing and applying the CSS, and executing the JavaScript, while it’s still downloading the HTML. This dramatically speeds up the perceived loading time because the browser doesn’t have to wait for the HTML to arrive, then parse it, discover the linked CSS and JS, and then make new requests for those files.

The core problem Server Push aims to solve is the "request waterfall." Traditional HTTP/1.1 requires a sequential process: download HTML -> parse HTML -> discover linked resources (CSS, JS, images) -> request those resources -> download them. Each step introduces latency. Server Push collapses this by allowing the server to anticipate the client’s needs and proactively send those linked resources. The server, knowing the HTML will require style.css, can initiate the push of style.css as soon as the request for index.html arrives.

Internally, HTTP/2 uses multiplexing to handle multiple requests and responses over a single TCP connection. Server Push leverages this by creating new streams for the pushed resources. When Nginx decides to push /style.css, it sends a PUSH_PROMISE frame to the client. This frame tells the client, "I’m going to send you /style.css on a new stream." The client can then decide whether to accept this push (if it already has the resource cached, it can send a RST_STREAM to cancel it) or acknowledge it. If accepted, the server then sends the actual style.css data on that stream. This is all done without the client explicitly asking for /style.css.

The primary lever you control is what gets pushed and when. In Nginx, this is done via the http2_push directive, typically within a location block that matches the primary resource (like your HTML page). You need to be careful not to push too much. Pushing unnecessary assets wastes bandwidth and can actually slow down your site if the client is already caching them or if the pushed resources are small and would have loaded quickly anyway. The decision to push should be based on resources that are guaranteed to be needed and are critical for initial rendering.

A common misconception is that Server Push is a magic bullet for all performance issues. It’s most effective for critical, render-blocking resources that are directly linked from the HTML and have a high probability of being requested. Pushing large, non-critical assets, or assets that might already be cached by the user, can be counterproductive. The server doesn’t know the client’s cache state by default, which is a significant limitation. Modern implementations often use Link: <url>; rel=preload headers as a more granular way to hint at resources, and while related, Server Push is about the server initiating the transfer proactively, not just hinting.

The next logical step after mastering Server Push is understanding how to optimize your Link headers for critical rendering paths.

Want structured learning?

Take the full Http2 course →