Strong SSL/TLS Cryptography in Apache and Nginx

I recently moved my homepage, Robert Love, from HTTP to HTTPS. My goal was to configure the server with strong cryptography while making less compromises for compatibility and against security than many of the "best practice" configurations others provide. I thought I'd share the result. Notably, I recommend a cipher suite ordering that enables Perfect Forward Secrecy (PFS) with AES-GCM, disables broken ciphers, neutralizes known attacks, and still works on nearly every browser. I provide configuration for both Apache and Nginx.

HTTPS Info for

Many feel encryption is the raison d'ĂȘtre of HTTPS, but I argue that the verification of identity and prevention of man-in-the-middle attacks are more important. Consequently, given the sophistication of today's adversaries, I believe HTTPS is important even for situations such as static content where you may feel encryption is of minimal value. I hope this guide inspires more webmasters to put all of their content under HTTPS.

Your Server's Certificate

Let's start with your digital certificate, which is at the core of HTTPS. The certificate enables clients to verify the identity of servers, through a chain of trust from your server's certificate through intermediate certificates and up to a root certificate trusted by users' browsers. Your server certificate should be 2048 bits in length. I really ought be recommending a 4096-bit certificate, but the computation required (for both the client and server) is currently cost prohibitive. Consequently, 2048 bits is a necessary compromise.

For a certificate authority, I recommend DigiCert, but plenty of folks are happy with StartSSL, who offers free certificates for personal websites.

Basic HTTPS Setup

I don't intend to make this a general purpose "how to configure SSL" guide, as there are many of those around. But here are basic SSL configurations, first for Apache:

<VirtualHost *:443>
    SSLEngine on
    SSLCertificateFile /etc/ssl/certs/your_cert
    SSLCertificateChainFile /etc/ssl/certs/chained_certs
    SSLCertificateKeyFile /etc/ssl/certs/your_private_key

And then for Nginx:

server {
    ssl on;
    ssl_certificate /etc/ssl/certs/your_cert_with_chain;
    ssl_certificate_key /etc/ssl/certs/your_private_key;
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 10m;

In Nginx, the ssl_certificate parameter is confusing. It expects your certificate plus any necessary intermediate certificates, concatenated together. Thus, to generate it, you'd do something like this:

$ cat your_cert chained_certs > your_cert_with_chain

Make sure all of these files are at least mode 0444, except your private key, which should be 0400.

Cipher Suite Configuration

With the basic configuration covered, let's get to the meat: My recommended cipher suite configuration. Getting this right is difficult, as you need to consider myriad issues ranging from attacks such as BEAST to disabling compromised algorithms such as RC4 to supporting antiquated crap browsers. What follows is an aggressive, strong cryptography configuration.

Here's my recommended cipher suite configuration for Apache:


SSLHonorCipherOrder on

And here's the same configuration for Nginx:


ssl_prefer_server_ciphers on;

What this does:

  • For the key exchange, we prefer ephemeral key-exchange algorithms that provide PFS. We favor Elliptic Curve Diffie-Hellman (ECDHE) over the multiplicative version (DHE), as the former is less processor intense for a similar level of security, but we support both.
  • For identity, we only support RSA. That's fine, as your certificate is RSA (DSA certs are uncommon).
  • For message ciphers, we favor AES over everything else. For AES, we favor the GCM version over CBC, as GCM is more efficient and not susceptible to BEAST, and we favor AES-256 over AES-128.
  • For message authentication, we favor SHA-2 with 256 or 384-bit digests.
  • We disable non-authenticated and non-encrypted suites, all of the legacy export-approved ciphers, Camellia, DES (but not 3DES), MD5, and pre-shared keys.
  • Controversially, we also disable RC4. This deserves a longer discussion.

Update: If you are willing to drop support for all versions of Internet Explorer on Windows XP—a fine choice given XP's end of life—I now provide an even more aggressive cipher suite configuration that only supports PFS key exchange and drops support for 3DES.

Disabling RC4: Not for the Faint of Heart

Disabling RC4 isn't a common recommendation; even so-called "strong crypto" recommendations include it. Some recommendations even prioritize RC4, as it is not susceptible to BEAST. But RC4 has a growing list of attacks against it, many of which have crossed the line from theoretical to practical. Moreover, there is reason to believe that the NSA has broken RC4—their so-called "big breakthrough."

Disabling RC4 has several ramifications. One, users with shitty browsers such as Internet Explorer on Windows XP will use 3DES in lieu. Triple-DES is more secure than RC4, but it is significantly more expensive. Your server will pay the cost for these users. Two, RC4 mitigates BEAST. Thus, disabling RC4 makes TLS 1.0 users susceptible to that attack, by moving them to AES-CBC (the usual server-side BEAST "fix" is to prioritize RC4 above all else). I am confident that the flaws in RC4 significantly outweigh the risks from BEAST. Indeed, with client-side mitigation (which Chrome and Firefox both provide), BEAST is a nonissue. But the risk from RC4 only grows: More cryptanalysis will surface over time. It is time to retire its use.

Cipher Suites in the Real World

In practice, this cipher suite ordering will yield TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 under Firefox and Chrome.

TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 is pretty close to ideal, so let's use it as an example of how to read cipher suites: Protocol-Key exchange and identity-WITH-Cipher-Message authentication.

In this case, we have:

  • TLS: Protocol is Transport Layer Security (TLS)
  • ECDHE: Key exchange is Elliptic Curve Diffie-Hellman, which provides Ephemeral keys (ECDHE)
  • RSA: Identity is via the RSA protocol and thus our certificate is RSA
  • AES_128_GCM: Cipher is AES, 128-bit key, in Galois/Counter Mode (GCM)
  • SHA256: Message authentication is SHA-2 with a 256-bit digest

Protocol Support: To SSL or not to SSL

To prevent downgrade attacks, we will also disable old SSL protocols. Somewhat confusingly, folks use "SSL" as a synecdoche for HTTPS. In fact, SSL is one particular HTTPS protocol—one which is now considered horribly broken. SSL was superseded by TLS 1.0 in 1999; TLS 1.2 is the latest version. Everyone disables SSLv2, but we will also disable SSLv3, as TLS 1.0 suffers a downgrade attack, allowing an adversary to force a connection to use SSLv3 and thus disable PFS.

For Apache:

SSLProtocol all -SSLv2 -SSLv3

For Nginx:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

This disables all versions of SSL, enabling only TLS 1.0 and up. All versions of Chrome and Firefox support at least TLS 1.0. With this change you will break Internet Explorer 6 and earlier, who top out at SSLv3, but who cares about those assholes.

HTTPS Compression Considered Harmful

To mitigate the CRIME attack, you need to disable SSL/TLS compression. It is off by default in most versions of Nginx, but Apache only recently made that the default. Best be explicit:

SSLCompression Off

DHE Parameters

Apache prior to version 2.4.7 and all versions of Nginx as of 1.4.4 rely on OpenSSL for input parameters to Diffie-Hellman (DH). Unfortunately, this means that Ephemeral Diffie-Hellman (DHE) will use OpenSSL's defaults, which include a 1024-bit key for the key-exchange. Since we're using a 2048-bit certificate, DHE clients will use a weaker key-exchange than non-ephemeral DH clients.

For Apache, there is no fix except to upgrade to 2.4.7 or later. With that version, Apache automatically selects a stronger key. For Nginx, we can generate a stronger DHE parameter:

$ openssl dhparam -out dhparam.pem 2048

And then tell Nginx to use it for DHE key-exchange:

ssl_dhparam /etc/ssl/certs/dhparam.pem;


Finally, if possible, you should enable HTTP Strict Transport Security (HSTS), which instructs browsers to communicate with your site only over HTTPS. For Apache:

Header add Strict-Transport-Security "max-age=15768000"

For Nginx:

add_header Strict-Transport-Security max-age=15768000;

You should put these in every VirtualHost/server block for HTTPS versions of your website, so that HTTPS request will receive the HSTS header in the reply. If you do that, you should also canonicalize HTTP requests to HTTPS. For Apache:

NameVirtualHost *:80
<VirtualHost *:80>
    Redirect permanent /

And for Nginx:

server {
    listen 80;
    listen [::]:80 ipv6only=on; # omit line if no ipv6
    charset utf-8;
    return 301$request_uri;

Software Versions

It is a pain in the ass, but you likely need to compile some software: You need the latest version of your web server software and (even more important) OpenSSL to support all of the ciphers discussed here, as well as functionality such as TLS 1.2 and fixes for vulnerabilities such as Lucky13. As of this writing, the latest OpenSSL is 1.0.1g. You really want (at least) that version.

In this guide, I've provided configuration for both Apache and Nginx, but after almost two decades of using the former, I now recommend the latter.

The End Result

For all your hard effort, this will earn you an "A+" grade and near-perfect SSL Labs Rating:

SSL Labs A+ Grade for

You cannot do better without silly sacrifices—for example, supporting only TLS 1.2 would earn you a 100 in "Protocol Support," but then only Chrome and Firefox 27 could access your site.

This configuration offers up the strongest cryptography provided by today's browsers, including PFS via ECDHE with AES in GCM mode as your cipher. For lesser browsers, the fallback crypto remains plenty tough—any browser in the last decade will find a compatible cipher suite of decent quality. The configuration disables anything with suspected vulnerabilities, most notably RC4. Modern browsers will use AES in lieu; antiquated crap will use 3DES. Next, the configuration disables not only SSLv2 but also SSLv3, which means Internet Explorer 6 is SOL but so prevents a nasty downgrade attack. Finally, the config enables HSTS and HTTP-to-HTTPS redirects, ensuring all traffic remains secure.

It is unfortunate there is only one cipher without issue, AES-GCM, available to HTTPS. Even if AES-GCM were perfect, we ought to have alternatives. I believe ChaCha20+Poly1305 is that alternative. ChaCha20+Poly1305 is now supported by Chrome. Once supported by OpenSSL, I'll update my recommended cipher suite. The ordering (AES-GCM over ChaCha20+Poly1305 or vice versa) isn't yet clear to me; ChaCha20 is likely to be much faster than AES without hardware acceleration. Chrome smartly adjusts the relative ordering of AES versus ChaCha20+Poly1305 based on the presence of hardware acceleration. Similar logic for Nginx is desirable.

For more information on ChaCha20+Poly1305, see this blog post and this IETF draft, both by my Google colleague Adam Langley.