This is a follow up on a story that I've published a few years ago. It still receives a huge amount of traffic so I decided to update it with the latest information.
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. In layman's terms, 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.
The rest of this walkthrough assumes that you already have your TLS certificates (or know where/how to get them).
Tell nginx to use TLS and HTTP2. To do that, add ssl and http2 parameters to listen directive.
server {
listen 443 ssl http2;
...
}
Before we forget, let’s disable SSL and old TLS versions. These protocols come with some serious issues and should not be used.
ssl_protocols TLSv1.2 TLSv1.3;
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-CBC:ECDH+AES128-CBC:DH+3DES:!ADH:!AECDH:!MD5;
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”.
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.
Tell nginx to use DH params:
ssl_dhparam /etc/nginx/certs/dhparam.pem;
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 this case the issues is "Let's Encrypt" and you can use the following command to download the 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;
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;
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;
}
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;
}
If you followed the steps above you should end up with a configuration like the following one:
server {
listen 443 ssl http2;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDH+AESGCM:ECDH+AES256-CBC:ECDH+AES128-CBC:DH+3DES:!ADH:!AECDH:!MD5;
ssl_certificate ...;
ssl_certificate_key ...;
ssl_dhparam /etc/nginx/certs/dhparam.pem;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/certs/lets-encrypt-x3-cross-signed.pem;
ssl_session_cache shared:SSL:40m;
ssl_session_timeout 4h;
ssl_session_tickets on;
add_header Strict-Transport-Security "max-age=31536000" always;
}
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 here are some resources that might be useful when configuring TLS on nginx.
Previously published at https://medium.com/@mvuksano/how-to-properly-configure-your-nginx-for-tls-564651438fe0