Hardening Your Web Server’s SSL Ciphers

There are many wordy articles on configuring your web server’s TLS ciphers. This is not one of them. Instead I will share a configuration which is both compatible enough for today’s needs and scores a straight “A” on Qualys’s SSL Server Test.

Disclaimer: I’m updating this post continually in order to represent what I consider the best practice in the moment – there are way too many dangerously outdated articles about TLS-deployment out there already.

Therefore it may be a good idea to check back from time to time because the crypto landscape is changing pretty quickly at the moment. You can follow me on Twitter to get notified about noteworthy changes.

If you find any factual problems, please reach out to me immediately and I will fix it ASAP.


If you configure a web server’s TLS configuration, you have primarily to take care of three things:

  1. disable SSL 2.0 (FUBAR) and SSL 3.01 (POODLE),
  2. disable TLS 1.0 compression (CRIME),
  3. disable weak ciphers (DES, RC4), prefer modern ciphers (AES), modes (GCM), and protocols (TLS 1.2).

You should also put effort into mitigating BREACH. That’s out of scope here though as it’s largely application-dependent.

Software and Versions

On the server side you should update your OpenSSL to 1.0.1c+ so you can support TLS 1.2, GCM, and ECDHE as soon as possible. Fortunately that’s already the case in Ubuntu 12.04 and later.

On the client side the browser vendors are starting to catch up. As of now, Chrome 30, Internet Explorer 11 on Windows 8, Safari 7 on OS X 10.9, and Firefox 26 all support TLS 1.2.


There used to be a bullet point suggesting to use RC4 to avoid BEAST and Lucky Thirteen. And ironically that used to be the original reason for this article: when Lucky Thirteen came out the word in the streets was: “use RC4 to mitigate” and everyone was like “how!?”.

Unfortunately shortly thereafter RC4 was found broken in a way that makes deploying TLS with it nowadays a risk. While BEAST et al require an active attack on the browser of the victim, passive attacks on RC4 ciphertext are getting stronger every day. In other words: it’s possible that it will become feasible to decrypt intercepted RC4 traffic eventually and the NSA probably already is. Microsoft even issued a security advisory that recommends to disable RC4. As of 2015, there’s even an RFC.

The String


You can test it against your OpenSSL installation using


to see what’s supported2.

You’ll get:

The string also prefers AES-256 over AES-128 (except for GCM which is preferred over everything else). It does so mostly for liability reasons because customers may insist on it for bogus reasons.

However quoth a cryptographer:

AES-128 isn’t really worse than AES-anythingelse, at least not in ways you care about

The very simplified gist here is that the only reason for having 256 bit keys are quantum computers which are less likely to become a problem than the key scheduling issues in AES-256. But let me stress that both are fine. It’s just that adding AES-256 ciphers doesn’t improve your security in practice.

So if AES-128 is fine for you, feel free to add an ‘:!AES256’ to the end of the cipher string like I do.


SSLProtocol ALL -SSLv2 -SSLv3
SSLHonorCipherOrder On

This works on both Apache 2.2 and 2.4. If your OpenSSL doesn’t support the preferred modern ciphers (like the still common 0.9.8), it will fall back gracefully but your configuration is ready for the future.

Please note: you need Apache 2.4 for ECDHE and ECDSA. You can circumvent that limitation by putting an SSL proxy like stud or even nginx in front of it and let Apache serve only plain HTTP.

TLS compression is a bit more complicated: as of Apache 2.2.23, it’s not possible to switch it off inside of Apache. For Apache 2.2.24+ and 2.4.3+ you can switch it off using:

SSLCompression Off

Currently the default is On. But that changed from 2.4.4 on.

The good news for Ubuntu admins is that Ubuntu has back ported that option into their 2.2 packages – and set it to off by default – so you should be fine. On RHEL/CentOS you used to have to set an environment variable but that has been fixed too and should be correct by default now.


ssl_prefer_server_ciphers On;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

TLS compression depends on the version of nginx and the version of OpenSSL. If OpenSSL 1.0.0 or later is installed, anything after nginx 1.0.9 and 1.1.6 is fine. If an older OpenSSL is installed, you’ll need at least nginx 1.2.2 or 1.3.2.

Have a look at this serverfault answer for more details.

TL;DR on TLS compression & nginx: if you’re using Ubuntu 12.04 or later you’re fine (OpenSSL 1.0.1/nginx 1.1.19).

CentOS/Red Hat Enterprise Linux 6

Although CentOS 6.5 is shipping an OpenSSL that is capable of ECDHE key exchange, it doesn’t ship an nginx and the nginx you get from nginx.org is compiled against an older OpenSSL. Therefore openssl ciphers will confusingly show you all the shiny ciphers but nginx just won’t offer it to the clients.

That’s terrible because it costs you PFS for IE browsers.

You’ll have to compile nginx yourself or source it from an alternative package index (EPEL for instance).


For TLS matters HAProxy shouldn’t be older than version 1.5.

   ssl-default-bind-options no-sslv3

   ssl-default-server-options no-sslv3

Bonus Points

Qualys updated their requirements on 2014-01-21 and the cipher suites here are still “A”–material. If you want an “A+” though, you’ll need to add HSTS headers too. That’s out of scope for this article but the linked Wikipedia article will get you started.


Make sure to test your server afterwards!

If you want to learn more about deploying SSL/TLS, Qualys’s SSL/TLS Deployment Best Practices are a decent primer.

For investigating the SSL/TLS behavior of your browser, How’s My SSL? will give you all the details you need.

The (Near) Future

2013 has galvanized the whole industry. This is a good thing. In 2012 barely anyone lost a thought about configuring their TLS ciphers, how many bits their certificates had, or even forward secrecy. That made it way too easy for the bad folks. Nowadays people are questioning their own practices, open source projects work on enhancing their TLS support, and the public started to listen to cryptographers again instead of discounting them as crazy tinfoil crowd.

Good things are shaping on the horizon and Google’s Adam Langley given the power of having control over both servers and the most popular browser is pressing ahead. Their servers widely support TLS 1.2 with AES-GCM. Chrome has the best TLS support already. Additionally, recent releases now have grown support for ChaCha20 which is an extremely fast yet secure stream cipher by Dan Bernstein and Poly1305 a great MAC of the same pedigree. Google uses them if the the client doesn’t have hardware acceleration for AES.

Now if people just stopped using old browsers and we could roll out SNI and mandatory TLS 1.2.


Since I’m keeping this up to date, I’m going to document changes for returning visitors:

I spoke at at multiple Python conferences about the Sorry State of SSL; you may want to check out the talk page and video!

Credits: The initial version of this article has been kindly proof-read by Christian Heimes. My current cipher string is based on one by Zooko Wilcox-O'Hearn who contrary to me is a cryptographer and has just launched a great secure cloud storage you should check out. The errors are all still mine.


  1. This will cut you off Internet Explorer 6 on Windows XP but you should do it anyway. IE 8, Chrome, and Firefox run just fine on XP. 

  2. The !DSS at the end is only for cosmetic reasons to keep the output of openssl ciphers shorter. It has no implication security-wise because the signature-algorithm is determined by the type of your server key. DSS is not commonly deployed in the wild and if you don’t want your server to use it, simply don’t use a DSS server key (which is very unlikely to happen anyway). The current widespread standard is RSA while ECDSA is trying to get traction. 

  3. Please don’t conflate RSA for authentication (which is okay) with RSA for key exchange (which is terrible because it lacks PFS).