Setting up Perfect Forward Secrecy for nginx or stud

There has been a lot of discussion recently about Perfect Forward Secrecy (PFS) and the benefits it can bring you, especially in terms of any kind of traffic sniffing attack. Unfortunately setting this up I found very few guides telling you exactly what you need to do.

The downside to PFS is that it uses more CPU power than other ciphers. This is a trade-off between security and cost.

Importantly, this guide is for sites that already have SSL setup with a valid certificate.

The key to PFS is the cipher: ECDHE – Elliptic Curve Diffie Hellman Encryption.

But before we configure the cipher we need to add some “randomness” to our Key. I will assume your key is in the mywebsite.com.pem file, as is compatible with both stud and nginx:

$ openssl dhparam -rand - 1024 >> mywebsite.com.pem

Note that this pem file must already exist, and is the file you concatenated together with your certificates and private key from whoever signed your SSL certificate request. Afterwards it will have a section at the end labelled -----BEGIN DH PARAMETERS-----.

Now it’s a simple matter of enabling the right ciphers in nginx or stud. One problem I had doing this is the version of stud shipped with Ubuntu 12.04 doesn’t support ECDHE, despite the version of openssl supporting it. So I had to download and install a new version of stud. I’m not sure about nginx, but you may need to do the same there.

First though let’s test if we can get everything working just using openssl. You’ll want two windows open for this. In one window create a server:

$ openssl s_server -accept 8888 -cert mywebsite.com.pem -pass stdin -cipher ECDHE-RSA-RC4-SHA
(you may need to press enter to get it to say "ACCEPT")

And in another window connect to it with a client:

$ openssl s_client -connect -cipher ECDHE-RSA-RC4-SHA

If it works fine, you will get lots of output about the certificate and the current connection, and anything you type in the client will appear in the server.

If it has not worked it will exit after showing something like:

139923546220192:error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure:s23_clnt.c:724:
no peer certificate available
No client certificate CA names sent
SSL handshake has read 7 bytes and written 90 bytes
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE

Now we know our .pem file works, let’s try again with stud. In our “server” window run:

$ stud --prefer-server-ciphers --ciphers='ECDHE-RSA-RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH' -f,8888 -b,8081 mywebsite.com.pem

This time we have added more ciphers so that clients that don’t support ECDHE can still connect. Now in our “client” window use the same openssl s_client command and ensure you can connect. Try a second time with the flag -cipher RSA to ensure that lesser clients can still connect.

Now you have all the pieces working. Just modify your start script for stud (we use runit for this, but there are plenty of other ways). Note we use a slightly longer cipher suite as recommended by SSLLabs and Qualys:


For Nginx all you need to do is add the following line to your configuration for your server:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;

Remember to test nginx/stud with openssl s_client once you have restarted it!


20 thoughts on “Setting up Perfect Forward Secrecy for nginx or stud

  1. In your very first command, shouldn’t it be “openssl dhparam …” instead of simply “dhparam” ?

    On CentOS 6.4, dhparam alone was not found.

  2. Thanks a lot for this, I’ve had PFS running on my site for a while but this helped me make it a bit more solid. One thing I’d like to know though is how do you check what cipher incoming client connections are using?

  3. I notice that when setting the above ssl_ciphers, SSLlabs says “Forward Secrecy: No”, even though I can connect fine with ECDHE using s_client and Firefox connections default to ECDHE….. any idea why it’s saying “no”?

    • I’m not entirely sure, but here’s another list of recommended ciphers:


      (change the spaces for colons for nginx)

      Also make sure you have this config:

      ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
      ssl_prefer_server_ciphers on;

    • It’s saying “no” because ECDHE suites alone are not sufficient for robust Forward Secrecy support (not all clients support EC). You also need to support DHE suites.The reporting in SSL Labs has since been improved: it now reports something like, yes (most browsers), modern browsers, some, and no.

  4. Yes, using their recommended cipher set gives a “yes”, it looks like forward secrecy has to work for all browsers than it can work for when it does its simulated handshake process. If any that could use it don’t the overall result is a “no”.

  5. Kevin says:

    I think a critical piece of information is which openssl library to use, because the actual available ciphers are determined at compile time. On CentOS 6.4, the exact cipher configuration string you provided doesn’t work. For one, CentOS only has openssl 1.0.0, and for another, none of the third-party RPMs I found so far have the right cipher combinations.

    It looks as if the only option for CentOS is to compile from source.

    • Absolutely true. I had to recompile stud to get things to work, but some may need to build a newer openssl. This is a pain, but it will get easier over time.

  6. Pingback: Now using ‘Perfect forward secrecy’ | Lynthium

  7. Pingback: Stripe WebHooks don’t work with TLS | Matt's Hacking Blog

  8. I tried hard setting keys with ecparm but failed, any help will be appreciated.

    $ openssl ecparam -genkey -name prime256v1 -out ssl.key
    $ openssl req -new -key ssl.key -out ssl-self.csr
    $ openssl req -x509 -days 365 -key ssl.key -in ssl-self.csr -out cert.pem

    vh-nginx file:

    ssl on;
    ssl_certificate_key /root/ssl/sinful.key;
    ssl_certificate /root/ssl/sinful-ca.pem;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;

    my website stops working .. :( .. but without elliptical curve, works fine with TLS 1.2, 256 encryption.

    Server Side: ciphers are enabled and well supported.

  9. Funny Part is , its working from command line where client is able to connect.

    openssl s_client -port 8888
    Protocol : TLSv1.2
    Cipher : ECDHE-ECDSA-AES256-GCM-SHA384

    but when I add it in nginx.conf file, website stops. doesn’t load.
    “This webpage is not available” this is what I get…

    Please help ….

  10. In order to test if everything is working I had to add the path to the ssl certificates:

    openssl s_server -accept 8888 -key my_server.key -cert my_server.crt -pass stdin -CApath /etc/ssl/certs/ -cipher ECDHE-RSA-RC4-SHA


    openssl s_client -connect -CApath /etc/ssl/certs/ -cipher ECDHE-RSA-RC4-SHA

    BTW: I’ve read: ‘with the current stable nginx versions it’s no longer necessary to do the dhparam trick, and in fact it’s downright discouraged’ but can’t find other ‘evidence for this claim. What is your take on this?

    • I have no take on it whatsoever. I’m not a security expert – just someone who has to setup servers from time to time.

      Thanks for your comment – I’ll leave the original as-is as it worked for me. It may have been an OS issue.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s