How to get a 100% score on SSL Labs (Red Hat/CentOS 7.x, Apache, Let’s Encrypt)

There’s an Nginx version of this tutorial already, but since some people prefer/have to use Apache, here’s how to get 100% on SSL Labs’ server test with it.

For extra fun, instead of Debian/Ubuntu, we’ll be using Red Hat/CentOS (more precisely, CentOS 7.6 with the latest updates as of January 10th, 2019, but this shouldn’t change for any other 7.x versions of either CentOS or RHCE). And we’ll keep firewalld and SELinux enabled 1, even though, as far as I know, in most companies (well, at least the ones I’ve worked at) it’s, typically, official policy to disable both. 🙂

So, let’s assume you already have a CentOS/RHEL machine, with internet access and the default repositories enabled. First, we’ll enable the EPEL repository (to provide Let’s Encrypt’s certbot):

yum install

Now we’ll install certbot (which will also install Apache’s httpd and mod_ssl as dependencies) and wget (which we may need later):

yum install python2-certbot-apache wget

To be run certbot and allow it to verify your site, you should first create a simple vhost. Add this to your Apache configuration (e.g. a new file ending in .conf in /etc/httpd/conf.d/):

<VirtualHost *:80>
    DocumentRoot "/var/www/html"

Now add Apache’s ports to firewalld, and start the service:

firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --reload

systemctl enable httpd ; systemctl restart httpd

Run certbot to create and begin using the new certificate:

certbot --apache --rsa-key-size 4096 --no-redirect --staple-ocsp -d

You should now have the virtual host configured in /etc/httpd/conf.d/ssl.conf . Let’s add a few lines to it (inside the <VirtualHost></VirtualHost> section):

Header always set Strict-Transport-Security "max-age=15768000"
SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite          TLSv1.3 TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384
SSLHonorCipherOrder     on
SSLCompression          off

(Don’t mind the “TLSv1.3” line for now, it won’t be used — if at all  — until later in this post, and won’t have any effect if it isn’t.)

This should get you an A+ rating, with all bars at 100% except for Key Exchange. A good start, right? 🙂 However, that final bar can be a bit tricky. The problem here is that SSL Labs limits the Key Exchange rating to 90% if the default ECDH curve is either prime256v1 or X25519, the first of them being the OpenSSL default… and Apache 2.4.6 (the version included in all 7.x RHELs/CentOSes) doesn’t yet include the option to specify a different list of curves (of which secp384r1 will give a 100% rating, and won’t add any compatibility issues).

Assuming you don’t want to install a non-official Apache, then the only way (as far as I know) to specify a new curve is to add the results of running:

openssl ecparam -name secp384r1

to your certificate file. But Let’s Encrypt certificates are renewed often, so you may need some trickery here — such as adding something like this to root’s crontab:

*/5 * * * * grep -q "EC PARAMETERERS" /your/certificate/file.crt || openssl ecparam -name secp384r1 >> /your/certificate/file.crt

Ugly, I know. 🙂

Alternatively, you can upgrade Apache to a newer version. CodeIT (for instance) provides replacement packages for RHEL and CentOS, which you can use by doing:

cd /etc/yum.repos.d && wget`rpm -q --qf "%{VERSION}" $(rpm -q --whatprovides redhat-release)`.repo
yum -y update httpd mod_ssl

This will update Apache from 2.4.6 to 2.4.37 (as of January 10th, 2019), and so you can add the following to the vhost:

SSLOpenSSLConfCmd     Curves secp384r1:X25519:prime256v1

However! This will still not give 100%, since CoreIT’s Apache appears to come with a newer OpenSSL (1.1.1) statically linked to it, which means it’ll enable TLS 1.3 by default. And there’s nothing wrong with it… except that, from all the tests I’ve performed, Apache ignores the specified order of the ECDH curves when using TLS 1.3. With the line above, it’ll use secp384r1 for TLS 1.2, but X25519 for TLS 1.3 — and that’s enough to lower the Key Exchange bar to 90% again. 🙁

Given this, there are now several options:

1- disable TLS 1.3 (it’s still relatively new, and most people are still using 1.2): change the line:

SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1


SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.3

2- force secp384r1: change the “SSLOpenSSLConfCmd Curves” line to simply:

SSLOpenSSLConfCmd       Curves secp384r1

3- ignore this situation altogether: X25519 is considered to be as secure as secp384r1, if not more; even SSL Labs mentions not penalizing it in the future in their (early 2018) grading guide — they just have been a bit lazy in updating their test. It’s quite possible that a future update of their tool will give a full 100% score to X25519.

Anyway, most of this was done for fun, for the challenge of it.  🙂

  1. the latter shouldn’t make any difference here, though

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.