Recommended ssl_ciphers for security, compatibility - Perfect Forward secrecy

First, let's go over how cipher suite negotiation works, very briefly. For example, we can use the TLS 1.2 document RFC 5246 starting at section 7.4.1.2 to see, in the short short form:

  • ClientHello: The client tells the server which cipher suites the client supports
  • Now the server picks one
    • I'll discuss how to control which one it picks next!
  • ServerHello: The server tells the client which cipher suite it has chosen, or gives the client a failure message.

Now, as to the actual selection. I've used the nginx ssl module documentation, the Qualys 2013 article on Configuring Apache, Nginx, and OpenSSL for Forward Secrecy, and the Hynek Hardening Your Web Server’s SSL Ciphers article for reference. The latter two cover both Apache and Nginx (as both use OpenSSL as a base).

Essentially, you need to tell Nginx to use the order you select, and you need to select an order. To see what the results of that order would be, you can use the OpenSSL command line, e.g.

openssl ciphers -v 'EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA256:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EDH+aRSA+AESGCM:EDH+aRSA+SHA256:EDH+aRSA:EECDH:!aNULL:!eNULL:!MEDIUM:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4:!SEED'

NOTE: You may want to remove :!3DES from that string; 3-key triple-DES isn't efficient, but it is still secure in and of itself to more or less 112 bits of security, and is very, very common.

Use the above command to determine which cipher suites will be most preferred and least preferred in your configuration, and change it until you like the results. The references I've given have their own strings; I amended it slightly to get the above example (removing RC4 and SEED, and putting every TLS 1.2 cipher suite above any 'SSLv3' cipher suite, for example).

Then, for Nginx in particular, you would alter your configuration file to include something like:

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

Add in SSLv3 to ssl_protocols if you really insist on it.

The ssl_prefer_server_ciphers will inform nginx to use the order we specify, and ignore the order the client presents their cipher list in. Now, if the only shared cipher suite between the ClientHello and the list OpenSSL ciphers -v ... gives is our least preferred cipher, that's of course what nginx will use. If nothing matches, then we send the client a failure notice.

The ssl_ciphers command is the meat of the choice, here, as nginx will inform OpenSSL of our preferred cipher suite list. Please, please use the openssl ciphers -v command to see the results you get on your platform. Ideally, check it again after changing OpenSSL versions.

Also, please read Scott Helme's article on Setting up HSTS (HTTP Strict Transport Security) in nginx, which will allows a host to enforce the use of HTTPS on the client side. Be sure to include the HSTS header inside the http block with the ssl listen statement.

Edited to add: At least after this (if not before also), go to Qualys SSL Labs to see HTTPS security information and to Test Your Server that's been kept pretty well up to date for the last few years. Recommendations change regularly, and sometimes even frequently reverse themselves (RC4, for example, what nearly whiplash inducing). You can also even Test Your Browser!


Mozilla has an online tool that will help you choose the correct cipher suite.

https://mozilla.github.io/server-side-tls/ssl-config-generator/

It will let you input your server version, software version, etc. and then choose between a balance of security and legacy support.


OpenSSL naturally will prefer newer MACs for otherwise-equivalent cipher suites. For example, the lengthy openssl ciphers -v output for your cipher string starts with:

ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2  Kx=ECDH        Au=RSA    Enc=AESGCM(256)    Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2  Kx=ECDH        Au=ECDSA  Enc=AESGCM(256)    Mac=AEAD
ECDHE-RSA-AES256-SHA384        TLSv1.2  Kx=ECDH        Au=RSA    Enc=AES(256)       Mac=SHA384
ECDHE-ECDSA-AES256-SHA384      TLSv1.2  Kx=ECDH        Au=ECDSA  Enc=AES(256)       Mac=SHA384
ECDHE-RSA-AES256-SHA           SSLv3    Kx=ECDH        Au=RSA    Enc=AES(256)       Mac=SHA1
ECDHE-ECDSA-AES256-SHA         SSLv3    Kx=ECDH        Au=ECDSA  Enc=AES(256)       Mac=SHA1

Of course, TLS will only use cipher suites supported mutually by both the server and client, and neither Chrome nor Firefox support HMAC-SHA256 cipher suites. Since HMAC-SHA1 (and even HMAC-MD5) are still considered secure, I believe their developers (and those of NSS, the TLS library they both use) are skeptical of wasting developer effort and TLS handshake size adding new, unnecessary, and backwards-incompatible cipher suites.

Look at, for example, Chrome 33's supported cipher suites in order of preference:

  1. ChaCha20-Poly1305,
  2. AES-128-GCM,
  3. AES-256-CBC with HMAC-SHA1,
  4. RC4 (ugh) and AES-128-CBC with HMAC-SHA1, ...

OpenSSL doesn't support ChaCha20-Poly1305. If yours doesn't support AES-GCM either (???) and uses an RSA certificate, ECDHE-RSA-AES256-SHA is naturally the cipher suite Chrome will use. (Firefox 29 would use ECDHE-RSA-AES128-SHA.)

(The "SHA-256" cipher suites you've seen being used on other websites are presumably ChaCha20-Poly1305 or AES-128-GCM, which are AEADs that do not make use of HMAC, but whose cipher suites use SHA-256 in the PRF.)