Implementing SSL Perfect Forward Secrecy in NGINX Web-Server

Copyright © 2014 Ben Johnson

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:

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, and the SSL certificate chain and NGINX headers that are sent can be reviewed at

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 undue burden on the system's CPU, but with modern computing power, this seems like a worthwhile compromise. For more information, see 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_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:
# 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 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:,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 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+."


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


Share this page:

Suggested articles

6 Comment(s)

Add comment


By: Wes Johnson

The ciphers suggested in this article actually led to a poorer ssllabs / Qualys test result than when I didn't have PFS enabled. I'd suggest updating this article with the info at Qualys: 

Specifically, the following ciphers led me to an A rating:


 Thanks for the article though, led me to the right place.


Thank you for the comment, Wes. A couple of observations, however:

The only difference in the cipher-suite string in the cited article vs. the string in your comment is that you removed " !MEDIUM" and added " +RC4 RC4" in its place. I don't see how this measure could possibly increase your score, given that removing " !MEDIUM" should lower your score (if anything), and likewise adding " +RC4 RC4" should lower your score (if anything).

With regard to the rationale, " !MEDIUM" removes support for medium-strength ciphers, and while perhaps not substantially insecure in the context of TLS, adding " +RC4 RC4" cannot possibly make your implementation more secure. (For more information regarding RC4, see and

Also, I just tried using the string that you supplied and my overall score doesn't change, not do any of the four sub-scores (they remain A+, 100, 95, 90, 90).

Are you sure that you didn't make several changes at once and misinterpret the test results?

By: timothy smy

after running service nginx restartfist time in your howto got

Job for nginx.service failed. See 'systemctl status nginx.service' and 'journalctl -xn' for details.

By: timothy smy

this is a update to my last post

[email protected]:~# nginx -t -c /etc/nginx/nginx.confnginx: [warn] duplicate value "TLSv1" in /etc/nginx/perfect-forward-secrecy.conf:1nginx: [warn] duplicate value "TLSv1.1" in /etc/nginx/perfect-forward-secrecy.conf:1nginx: [warn] duplicate value "TLSv1.2" in /etc/nginx/perfect-forward-secrecy.conf:1nginx: [emerg] "ssl_prefer_server_ciphers" directive is duplicate in /etc/nginx/perfect-forward-secrecy.conf:2nginx: configuration file /etc/nginx/nginx.conf test failed

By: CodingKiwi

Dude. My Server was busy generating that pem file for like... 5 days or so. I ran out of food and water, all I could see was dots and plus-signs. Send Help.

By: Victor Tavares

Thank you very much, mate. Your solutions worked awesome to me. I just had to change the RC4  to  !RC4 at ssl_ciphers  to get A rating, too.


Thanks aigan!