How to properly configure your nginx for TLS

It’s quite easy to get nginx configured to use TLS. It’s a little bit more difficult to configure it to do it properly. In this article I will try to explain what different configuration options are and give you an example configuration that you should be able to adjust to your needs.

Nginx does a great job as a “TLS termination” server. TLS termination means that nginx is the “other” end of your TLS connection — the one to which your browser talks. Establishing a TLS connection requires a handshake which can be quite lengthy. Having said that, there is one really good reason why you want your nginx server to be as performant as possible: Your users initial page load is directly impacted by this. This is the most critical point in time for your users. This is the time when the user paints his/hers impression of your company (and not just your product). You want the first impression to be as good as possible.

0. Get TLS certificates

The rest of this walkthrough assumes that you already have your TLS certificates (or know where/how to get them).

1. Enable TLS and HTTP2

To get your nginx to server to use TLS we first need to tell it to use it. To do that, add ssl and http2 parameters to listen directive.


server {
listen 443 ssl http2;
...
}

2. Disable SSL

Before we forget, let’s disable SSL. SSL is very old and it has some serious issues.

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

3. Optimise cipher suites

Cipher suites are the core of TLS. This is where encryption happens.

First we need to configure nginx to tell clients that we have a preferred list of ciphers that we want to use.

ssl_prefer_server_ciphers on;

Cipher suite can have profound implications on both performance and security of the connection. Choosing which ones to enable or disable is a whole new game. Following is a list of good cipher suites you can start with:

ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

4. DH Params

You should also specify your own Diffie-Hellman (DH) key exchange parameters. I won’t go into too much details what DH key exchange is. What you should know about it is that it is a protocol which allows two parties to negotiate a secret without ever putting that secret on the wire. It is pretty impressive piece of “artwork”.

Tell nginx to use DH params:

ssl_dhparam /etc/nginx/certs/dhparam.pem;

You can use openssl dhparam to generate parameters:

openssl dhparam 2048 -out /etc/nginx/certs/dhparam.pem

Generate DH parameters with at least 2048 bits. If you use 4096 bits for your TLS certificate you should match it in DH parameters too.

5. Enable OCSP stapling

To have a secure connection to a server client needs to verify certificate which server presented. In order to verify that certificate is not revoked client (browser) will contact issuer of the certificate. This adds a bit more overhead to connection initialisation (and thus our page load time).

We can tell our nginx server to get a signed message from OCSP server and then, when initialising a connection with some client, staple it to the initial handshake. This way client can be confident that certificate is not revoked and does not need to explicitly ask OCSP server.

It is also necessary to verify that OCSP response is not tampered with. For OCSP verification to work, the certificate of the certificate issuer, the root certificate, and all intermediate certificates should be configured as trusted using the ssl_trusted_certificate directive. As an example, if you’re using Let’s encrypt certificates you should download their certificate in “pem” format from https://letsencrypt.org/certificates/. You can use openssl x509 command to check who is the Issuer of the certificate:

openssl x509 -in /etc/nginx/certs/example.crt -text -noout

In case of the Issuer above you can use the following command to download correct certificate:

wget -O /etc/nginx/certs/lets-encrypt-x3-cross-signed.pem \
"https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem"

Now you have all the pieces needed to enable OCSP stapling on nginx. All you need to do is add the following to your server configuration:

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/certs/lets-encrypt-x3-cross-signed.pem;

6. Enable HSTS

In order to achieve the best performance and be able to consume benefits of HTTP2 it is mandatory to use TLS. HSTS is a feature which allows a server to tell clients that they should only use secure protocol (HTTPS) in order to communicate with it. When a (complying) browser receives HSTS header it will not try to contact the server using HTTP for a specified period of time.

To enable HSTS add the following headers to your nginx configuration file:

add_header Strict-Transport-Security "max-age=31536000" always;

If you want to include all subdomains as well add the following line too:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

7. Optimise SSL session cache

Creating a cache of TLS connection parameters reduces the number of handshakes and thus can improve the performance of your application. Caching is configured using ssl_session_cache directive. Default, “built-in” session cache is not optimal as it can be used by only one worker process and can cause memory fragmentation. It is much better to use shared cache.

Another parameter that effects number of handshakes that happen throughout lifetime of a server is ssl_session_timeout. By default it is set to 5 minutes. You should set it to something like 4hrs. Doing this will require you to increase the size of cache (as more information will need to be stored in it).

As a reference, a 1-MB shared cache can hold approximately 4,000 sessions.

Add the following to your nginx server config in order to set TLS session timeout to 4hrs and increase size of TLS session cache to 40MB:

server {
ssl_session_cache shared:SSL:40m;
ssl_session_timeout 4h;
}

8. Enable session tickets

Session tickets are an alternative to session cache. In case of session cache information about session is stored on the server. In case of session tickets, information about session is given to the client. If a client has a session ticket, it can present it to the server and re-negotiation is not necessary. Set ssl_session_tickets directive to on:

server {
...
ssl_session_tickets on;
}

9. Conclusion

If you followed the steps above you should end up with a configuration like the following one:

server {
# Enable TLS and HTTP2
listen 443 ssl http2;

# Use only TLS
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  # Tell client which ciphers are available
ssl_prefer_server_ciphers on;
ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
  # Use our own DH params
ssl_dhparam /etc/nginx/certs/dhparam.pem;
  # Enable OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/lets-encrypt-x3-cross-signed.pem;
  # Enable HSTS
add_header Strict-Transport-Security "max-age=31536000" always;
  # Optimize session cache
ssl_session_cache shared:SSL:40m;
ssl_session_timeout 4h;
  # Enable session tickets
ssl_session_tickets on;
  ...
}

If you now head off to https://www.ssllabs.com/ssltest/analyze.html and test your web server you should be presented with an A result.

And finally, following are some resources that you will find useful when configuring TLS on your nginx: