Setting up HTTP/2 for Nginx

In Racing Trim

Freestyle: Server Push

HTTP/2 also offers another feature that significantly improves the user experience: server push, with which it is quite possible to predict which resources (images, JavaScript, etc.) the client will need, to display a web page fully.

To begin, it is enough to look into the header of an HTML file, which is usually a colorful mix of CSS and JavaScript resources. This information is also available to the web browser, but only after it has downloaded and parsed the initial HTML document. Only then does it send a request for the additional resources to the server and must wait for another network roundtrip before the responses arrive.

With server push in HTTP/2, you can send these additional resources directly after the response for the initial HTML file – before the browser even knows it will need them. The browser accepts the additional responses and stores them in the browser cache. If a request for a specific resource is then made during parsing, the response comes immediately and at high speed from the local cache; a network roundtrip to the server is therefore unnecessary.

A requirement of the resources that are to be sent by server push is that they must be bufferable. The browser must be able to read from the response headers, which work the same way for server push as for a normal request, in that the response will remain valid for a while. Nginx handles this requirement with the usual expires configuration directive. Once set, you can use the http2_push directive to push individual files to the client. In the example

location /index.html {
  http2_push /css/styles.css;
  http2_push /js/scripts.js;

the styles.css and scripts.js files are sent directly with the response to a request for index.html.

These actions can be tracked with Chrome DevTools by looking at the Initiator column. If Disable cache is enabled, the Push / … entry shows that the file arrived at the browser by HTTP/2 push. Be careful, though: Not all files the server has sent by push will appear here. If the current HTML page (so far) did not contain any request for a file, then it will not appear in the list. However, as soon as such a request is made, it appears there.

With Disable cache checked, however, the browser does not behave like a normal user. Alternatively, you can check the Nginx access log to see whether the server push is working correctly. Files sent by server push initially appear in the access log as normal requests, but if you add the keyword $msec to the Nginx log output,

log_format http2 '$time_iso8601 $msec$status $connection $http2 "$request"';

you can see that all requests were processed at exactly the same time (Listing 1), which indicates a push.

Listing 1

Server Push in Log

2020-11-22T12:01:10+01:00 1606042870.567 200 605 h2 "GET /index.html HTTP/2.0"
2020-11-22T12:01:10+01:00 1606042870.567 200 605 h2 "GET /css/styles.css HTTP/2.0"
2020-11-22T12:01:10+01:00 1606042870.567 200 605 h2 "GET /js/scripts.js HTTP/2.0"

If you know the behavior of users on your own site well, you can consider pushing the most frequently visited follow-up page directly:

location /index.html {
  http2_push /css/styles.css;
  http2_push /js/scripts.js;
  http2_push /<next-page>.html;

If the user follows this navigation path, the browser serves the corresponding request for /<next-page>.html from the local cache with virtually no time delay.

Note, though, that Nginx does not remember which resources have been sent to a client and are most likely cached there. Therefore, configuration errors can cause Nginx to send resources that are already available to the client, wasting network bandwidth. Especially for resources with very different cache durations, you should pay very close attention to this possibility.

Nginx as Proxy

When Nginx works as a proxy in front of an application server, customizing all details of the web application in the HTTP/2 push configuration is not practical. At this point, the http2_push_preload directive helps:

location /proxy/ {
  proxy_pass http://applicationserver/;

This directive processes link headers of upstream resources and sends resources marked with rel=preload automatically (i.e., whenever a response from the application server sends this header along). For example, with the following statement, Nginx automatically sends the /css/styles.css resource as a push to the client in addition to the response itself:

link: </css/styles.css>; rel=preload; as=style

This mechanism allows control of the push behavior directly from the web application. Because the web application probably has state management, it is also much easier to avoid unnecessary pushes at this point. For example, resources can only be sent by push at the start of a session.

Listing 2 shows an example Nginx configuration for HTTP/2 that can serve as a starting point for your own experiments.

Listing 2

Example Configuration

01 http {
03   log_format http2 '$time_iso8601 $msec $status $connection $http2 "$request"';
05   server {
07     listen 443 ssl http2; # TLS and HTTP/2
09     http2_idle_timeout 3m;
10     http2_max_requests 1000;
12     access_log /usr/local/var/log/nginx/http2.log http2;
14     ssl_certificate /test.crt;
15     ssl_certificate_key /test.key;
17     root /var/www/html;
19     # Required for server push:
20     location /css/ {
21       expires 3h;
22     }
24     location /js/ {
25       expires 3h;
26     }
28     location /index-2.html {
29       expires 3h;
30     }
32     # Example for server push
33     # Call: GET /
34     location /index.html {
35       # These files are sent by push,
36       # when the client calls /index.html:
37       http2_push /css/styles.css;
38       http2_push /js/scripts.js;
39       # expected next navigation destination:
40       http2_push /index-2.html;
41     }
43     # Example of Nginx as proxy with http2_push_preload.
44     # The (simulated) application server is below.
45     # Call: GET /applicationserver/
46     location /applicationserver/ {
47       proxy_pass http://localhost:1025/;
48       # Process link header with rel=preload:
49       http2_push_preload on;
50     }
51   }
53   # Simulation application server:
54   server {
55     list 1025;
56     root /var/www/html;
58     location ~ .html$ {
59       # Hints for http2_push_preload:
60       add_header link "</css/styles.css>; rel=preload; as=style";
61       add_header link "</js/scripts.js>; rel=preload; as=script";
62     }
63   }
64 }


Even without special configuration, enabling HTTP/2 brings a speed advantage to your website. With careful tuning of the maximum requests and idle times per connection, you can reduce the load on your server. Push mechanisms can also significantly improve the speed perceived by the user. In practice, implementing these optimizations will require the cooperation of the software development department, which in return, will reduce their efforts required for bundling and spriting.

Overall, the development of HTTP/2 clearly shows a considerable potential for optimization in closer coordination between software development and the web server infrastructure. With the release of the first draft of HTTP/3 in November 2020, it also became clear that such investments are worthwhile. Most likely, HTTP/3 will mainly optimize the multiplexing mechanisms introduced with HTTP/2 with respect to the underlying network layer. It is therefore safe to assume that, if HTTP/2 mechanisms are used sensibly, you will soon be able to benefit directly from the optimizations to be expected from HTTP/3. Therefore, the use of HTTP/2 already appears to be a worthwhile optimization that can be strongly recommended to every web server operator.


  1. Usage statistics for HTTP/2:
  2. RFC 7540:
  3. Changes in Nginx 1.10:
  4. HTTP/2 support in browsers:
  5. Let's Encrypt:

The Author

Oliver Gutperl has been involved in the optimization of server applications and the interaction between infrastructure and software development for more than 20 years. He is the author of the book Nginx richtig konfigurieren (Configuring Nginx Properly , in German).

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy ADMIN Magazine

Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus