Implementing SSL Perfect Forward Secrecy in NGINX Web-Server

This HOW-TO describes the process of implementing Perfect Forward Secrecy with the NGINX web server on Debian and Ubuntu systems. The process can readily be adapted to other GNU/Linux systems.

In short, Perfect Forward Secrecy ensures: "... that the compromise of one message cannot lead to the compromise of others, and also that there is not a single secret value which can lead to the compromise of multiple messages." For more information, see http://en.wikipedia.org/wiki/Forward_secrecy#Perfect_forward_secrecy.

When the Heartbleed vulnerability in OpenSSL was revealed in early 2014, it became increasingly clear that PFS is a must for any system that employs SSL/TLS in a serious capacity.

Should you wish to compare your results against mine, my reference implementation can be tested at https://www.ssllabs.com/ssltest/analyze.html?d=indietorrent.org, and the SSL certificate chain and NGINX headers that are sent can be reviewed at https://indietorrent.org.

Without further ado, let's configure NGINX to implement PFS.

Let's move into NGINX's configuration directory:

cd /etc/nginx/

We need to generate Diffie-Hellman parameters that are sufficiently strong. Some argue that 4096 bits is overkill and will cause an undue burden on the system's CPU, but with modern computing power, this seems like a worthwhile compromise. For more information, see the References section, below.

openssl dhparam -out dh4096.pem 4096

It's handy to have this configuration file, which is specific to the task at hand, compartmentalized in an include file; this makes it simpler to implement PFS across a large number of systems.

vi /etc/nginx/perfect-forward-secrecy.conf

Paste the following into the above file:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 \
EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 \
EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !MEDIUM";
ssl_dhparam dh4096.pem;

Modify the NGINX configuration to include the above file, by inserting the following line into NGINX's primary configuration file (by default, /etc/nginx/nginx.conf), at the bottom of (and within) the http {} block:

# See: https://community.qualys.com/blogs/securitylabs/2013/08/05/configuring-apache-nginx-and-openssl-for-forward-secrecy
# This MUST come AFTER the lines that includes .../sites-enabled/*, otherwise SSLv3 support may be re-enabled accidentally.
include perfect-forward-secrecy.conf;

Restart NGINX to make the changes effective:

service nginx restart

If the test at https://www.ssllabs.com/ssltest/analyze.html displays Session resumption (caching) No (IDs assigned but not accepted) in red, and the server implements SNI, add the following to the top-level http {} block (i.e., add to nginx.conf, just below where we made the previous additions):

# See: http://forum.nginx.org/read.php?2,152294,152401#msg-152401
ssl_session_cache shared:SSL:10m;

Again, restart NGINX to make the changes effective:

service nginx restart

The above test should no longer report this issue (even though the issue does not reduce the overall test score).

Taking it Further: Implementing HTTP Strict Transport Security (HSTS) with Long Duration

This is an easy one, and well worth doing, provided that:

  1. You want to force SSL for all resources for any host for which this header is set (i.e., every page on the website in question).
  2. You can live with not having the ability to accept and ignore SSL warnings for any resource requested from any host for which this header is set, such as "Domain Name Mismatch", etc. The very nature of HSTS is that warning and error conditions relating to the SSL certificate cannot be overridden.

I scoured the Internet for information regarding whether or not setting this header might have unintended consequences in browsers that do not support the header and came-up short. But, I was able to allay my concerns by testing this implementation in Internet Explorer 6, for example, and browsers in which HSTS is not implemented simply ignore the header. Perfect!

Simply add the following lines to the bottom of /etc/nginx/perfect-forward-secrecy.conf and save the changes:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
# This will prevent certain click-jacking attacks, but will prevent
# other sites from framing your site, so delete or modify as necessary!
add_header X-Frame-Options SAMEORIGIN;

A reload (instead of a restart) will suffice for forcing NGINX to pick-up these particular changes:

service nginx reload

It is possible to confirm that HSTS is working as intended by testing your implementation at https://www.ssllabs.com/ssltest/analyze.html. If HSTS is implemented correctly, you should see a green box just below your score, stating, "This server supports HTTP Strict Transport Security with long duration. Grade set to A+."

Congratulations!

You now have one of the most secure SSL/TLS implementations on the Internet.

References:

Copyright © 2014 Ben Johnson

Share this page:

6 Comment(s)