There are many wordy articles on configuring your web server’s SSL 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 SSL configuration, you have primarily to take care of three things:
- disable SSL 2.0, and – if you can afford it – SSL 3.0 (Internet Explorer 6 is the last remaining reason to keep it around; you can’t have elliptic curve crypto with SSL 3.0 and downgrade attacks exist),
- disable TLS 1.0 compression (CRIME),
- 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.0+ so you can support TLS 1.2, GCM, and ECDH as soon as possible. Fortunately that’s already the case in the current Ubuntu LTS.
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 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. Microsoft even issued a security advisory that recommends to disable RC4.
Until recently, Qualys preferred RC4 over CBC-mode ciphers and I gave you two cipher strings to choose from: one that gave you an “A” but used RC4 and one that gave you a “B” but was actually secure. Since they finally changed their mind – and as of Safari 7 there’s no mainstream browser left that is susceptible to BEAST – I can jump directly to the secure one:
You can test it against your OpenSSL installation using
openssl ciphers -v 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS'
to see what’s supported.
- Best possible encryption in all browsers.
- Perfect forward secrecy; if your web server, your OpenSSL, and their browser support it.
- It doesn’t offer RC4 even as a fallback. Although its inclusion at the end of the cipher string shouldn’t matter, active downgrade attacks on SSL/TLS exist and having RC4 as part of the the cipher string you potentially expose all of your users to it. Even IE 6 does 3DES just fine.
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 to keep your cipher suite shorter which will also expedite your TLS handshakes.
1 2 3
SSLProtocol ALL -SSLv2 SSLHonorCipherOrder On SSLCipherSuite ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
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 ECDH 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:
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. The solution on Red Hat based OS (RHEL, Fedora, CentOS, Scientific Linux…) is setting an environment variable inside of your Apache startup script:
1 2 3
ssl_prefer_server_ciphers On; ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS;
SSL 2.0 is off and the best protocols on by default. However it may be that you have some artifact from pre-TLS 1.2 times lurking somewhere in your config so it’s better to be explicit.
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.
For more details, have a look at this serverfault answer.
TL;DR on TLS compression & nginx: if you’re using Ubuntu Precise (i.e. the current LTS release) 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, their nginx is compiled against an older one. 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.
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, which is 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, its Canary 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.
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:
- 2014-04-11: Added note on CentOS/RHEL 6 and nginx+ECDHE.
RSA+AEShas been split into
RSA+AESGCM:RSA+AES. This is a very minor update that only matters if you have TLS 1.2 but neither ECDHE nor DHE (which is rather rare). It makes sure, that
RSA-AES-128-GCMis preferred over
RSA-AES-256-CBCin these cases. Since both are just fallbacks and you should use PFS ciphers, this is just minutiae.
Update: I’ll be speaking at PyCon 2014 in Montreal about the Sorry State of SSL; join me if you want to learn more about SSL and how to use it in Python!
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.