An nginx config for 2017

With HTTP/2 in every browser, load balancing with automatic failover, IPv6, a sorry page, separate blog server, HTML5 SSE and A+ HTTPS.

By Mike on 13th Feb 2017

Thanks to Konstantin Pavlov from nginx for his contributions to the config used in this article.

nginx (pronounced 'Engine X') has excellent official documentation but putting all the logic together can take a while. An average web app in 2017 might want:

HTTP/2 support in all browsers

For speed! One of the pages on our blog loads in 1.9s on HTTP 1.1. The same page loads in 600ms over HTTP/2.

IPv6 support

If you're working on IoT devices, which often require IPv6.

Load balancing between multiple app servers with automatic failover.

So you can upgrade your app without taking it offline.

A branded 'sorry' page

Just in case you break both the app servers at the same time.

A separate server that handles blogs and marketing content

So you can keep your blog independent of the main app and update it on its own schedule.

Correct proxy headers for working GeoIP and logging.

So your app servers can see the proper origin of browser requests, despite the proxy. Because asking customers for their country when you already know is a waste of their time.

Support for HTML5 Server Sent Events

For realtime streaming.

An A+ on the SSL Labs test

So the users can connect privately to your site.

The various www vs non-www, HTTP vs HTTPS combinations redirected to a single HTTPS site.

This ensures there's only one, secure copy copy of every resource for both clarity and SEO purposes.


We encourage you to check out the official nginx docs. However...

Since combining all that logic can take a while we've put everything together into this guide and a matching GitHub project.

Not just the config, but why we chose the options we did so you can make sensible choices yourself, and a diagram explaining how the config fits together. You can also go straight to the git repo to get the files - but keep reading, you need more more than just nginx to get HTTP/2 working!

But why not

.... PaaS load balancers?

PaaS load balancers are very easy to set up. On the other hand, Paas are often much slower than alternatives for one or both of these reasons:

... HAProxy?

We really like HAProxy and have used it in production for years. But as of writing HAProxy 1.7 still doesn't fully support HTTP/2. HAProxy can support passing traffic onto another server than supports HTTP/2 - causing a lot of confusion on the internet - but HAProxy can't terminate a HTTP/2 connection itself. The most recent update from HAProxy themselves was from November 2016:

Will HTTP/2 be on the roadmap of 1.8 or 2.0?

Yes definitely. But I know too well that a community-driven project cannot have a roadmap, just hints for contributors. Also I am a bottleneck because while I review and integrate code I cannot develop. I thought I would have H2 in 1.6 if you remember :-) So let's say that we'll put a lot of efforts into getting H2 in 1.8. I also count a lot on SPOP to help move some contribs outside of the core and to release some of my time :-)

The person writing is Willy Tarreu, CTO and Lead Software Developer of HAProxy.

So until HAProxy fully supports HTTP/2, your simplest solution is nginx.

... a more complex solution that solves problems larger businesses have?

Once you have regular income from your app and a couple of million users you'll have many other considerations. You'll definitely have more than a couple of app servers and you'll want autoscaling, more complex phased deploys, and all kinds of other fun. But to get there, you'll need your app running on a fast, properly configured web server that doesn't go down for upgrades.


Getting an OS for your load balancer

Oh, and by the way:

The only LTS Linux distro that can serve HTTP/2 right now is Ubuntu 16.04.
RHEL 8, CentOS 8 and Debian 9 will in future.

So HTTP/2 works on every current browser. For servers, though, you're incredibly limited:

The nice thing: distros new enough to have openssl 1.02 are likely to have a new nginx (anything newer than 1.9.5 supports HTTP/2). Ubuntu 16.04 has 1.10.0:

apt-get install nginx

Configuring nginx

We've made CertSimple's nginx config with some specific goals:

Here's a diagram of how it all fits together:

Set up our redirect servers

A server in nginx-speak, is a virtual server than handles connections for a specific server name or IP. These are the pink boxes in the diagram above.

Users might visit our site with or without the www, and with https:// or just http://. So modern sites have four nginx servers: each variant of non-www or www www, plaintext or HTTPS. One of those servers does most of the work - we like HTTPS/non-www since HTTPS is needed for current browsers and non-www is short. The other 3 servers redirect permanently to the main server.

# Redirect www HTTP clients to non-www HTTPS permanently
# See http://nginx.org/en/docs/http/converting_rewrite_rules.html
server {
    listen 80;
    listen [::]:80;
    server_name www.example.com;
    return 301 https://example.com$request_uri;
}

# Redirect www HTTPS clients to non-www HTTPS permanently
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.example.com;
    include conf.d/https.conf;
    return 301 https://example.com$request_uri;
}

# Redirect non-HTTPs clients to non-www HTTPS permanently
server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    return 301 https://example.com$request_uri;
    include conf.d/https.conf;
}

PS. If you have't already done it, tell Google Search Console which server you prefer too!

Set up our main HTTP/2 server

Our main server terminates HTTPS, and passes a series of locations onto different backend servers via plain HTTP 1.1. A location is just a match to a URL path.

Here's our main server:

# Single HTTPS non-www server all browsers will talk to
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com;
    include conf.d/https.conf;

    # Tilde means case-sensitive RE match
    location ~ ^(/help|/blog|/images/blog) {
        proxy_pass http://localhost:8000;
        include conf.d/proxy.conf;
    }

    location / {
        proxy_pass http://app; # See 'upstream' directive above
        include conf.d/html5-sse.conf;
        include conf.d/proxy.conf;
    }

    location /nginx_status {
        stub_status on;
        access_log off;
        allow 127.0.0.1;
        deny all;
    }

    include conf.d/error-page.conf;
}

Set up HTTPS!

Both our HTTPS www-redirector and our main site need HTTPS, which we include via conf.d/https.conf:

# This is using the 'intermediate' config, you may prefer 'modern'
# Check https://mozilla.github.io/server-side-tls/ssl-config-generator/ and update this file!
ssl_certificate /etc/https/cert-and-intermediate.pem;
ssl_certificate_key /etc/https/private-key.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
# openssl dhparam -out dhparam.pem 2048
ssl_dhparam /etc/https/dhparam.pem;

# intermediate configuration. tweak to your needs.
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
ssl_prefer_server_ciphers on;

# HSTS (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000;

# OCSP Stapling
# fetch OCSP records from URL in ssl_certificate and cache them
ssl_stapling on;
ssl_stapling_verify on;

# Verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate /etc/https/root-and-intermediate.pem;

resolver <IP of your DNS server>;

As the file itself notes, you should immediately visit the Mozilla TLS Generator to get the latest cipher suites and TLS versions - these change over time as weaknesses are discovered in older crypto mechanisms.

Thanks Drake. Updated the file yet? Good.

You'll also combine the files you get from your certificate provider together to make the various PEM files. Just concatenate them (using the Unix cat command - yes that's why it's called 'cat') or a text editor. Order doesn't matter since the -----RFC 7468 HEADERS----- and the contents inside indicate what each is.

BLATANT PLUG

Speaking of HTTPS: we're CertSimple, a startup focused on EV HTTPS. If it's important to prove a real business controls your website, we're really good at that.

END BLATANT PLUG

Add neccessary proxy headers

For the major location we also include conf.d/proxy.conf which will add the necessary headers for GeoIP and proper logging. Without these, it would seem like all the users were connecting from the load balancer! Here's proxy.conf:

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;

Handle HTML5 SSE

Pretty much everything we make these days needs realtime, and we find HTML5 SSE simpler than websockets. However you'll need to tell nginx to handle them, which we do via html5-sse.conf.

proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;

Before you deploy your new load balancer

Get a metric for how long your current pages load!

Test in Chrome since you want to make sure ALPN works. Start an incognito window, open Developer Tools. Since's HTTP/2's multiplexing affects nearly every part of the conection, check the Network tab and the Load stat on the bottom right. Run a few reloads and record the average.

Review your config

Don't just blindly copy lines without understanding them. Don't need a feature on a paticular server or location? Remove it!

Test your config without restarting nginx

Just run:

nginx -t

For nginx's own config tester.

Migrating from an old load balancer to a new load balancer on a new IP?

If you're switching from an old load balancer to a new one, you'll need to plan things.

First test it out on a local machine: modify a hosts file on a testing box to point to a new server, and test from that machine before you do anything else. Check different backends, HTML5 SSE and web socket performance, and anything else that could break.

Look good? Revert your hosts file and let's plan a proper cutover.

Now you're deployed

Scan your site with SSL Labs scan. An A+ should be easly achievable. Fix anything that needs fixing.

Open Incognito window, start DevTools and add the 'Protocol' column:

Yes, h2 is HTTP/2. No, we don't know why.

Now the fun part: check the load times of those URLs you measured earlier. We've had 3x speedups across a bunch of pages since moving to HTTP2.

Mike MacCana, founder at CertSimple.

CertSimple makes EV HTTPS fast and painless.

An EV HTTPS certificate verifies the company behind your website. But getting verified is a slow painful process. CertSimple provides EV HTTPS certificates 40x faster than other vendors. We check your company registration, network details, physical address and flag common errors before you pay us, provide verification steps specific for your company, update in realtime during the process, and even check your infrastructure to help you set up HTTPS securely.
Verify your site now!