I saw the HTTP vs HTTPS Test website earlier this week and was blown away by the performance jump between HTTP 1.1 and SPDY on a TLS-encrypted connection. With the introduction of Let’s Encrypt a few years ago, you no longer need to buy security certificates in order to provide a secure connection to your site - so I decided to see how long it would take me to switch my blog to HTTPS.
The first tutorial I tried was DigitalOcean’s How to Secure Apache with Let’s Encrypt on CentOS 7. Since Let’s Encrypt apparently prefers (requires?) separate domains to be configured with separate virtualhost .conf files, that tutorial spends a lot of time explaining how to set up your vhosts, which I had already done.
Step 1 - Server Dependencies
Install access to the EPEL repository and install the
mod_ssl Apache module (prerequisites for the Let’s Encrypt client program
$ sudo yum install epel-release mod_ssl
Step 2 - Let’s Encrypt Client
certbot from the EPEL repo that you now have access to:
$ sudo yum install certbot
Note: this only installs the basic
certbot client. If you want to get the Apache plugin, install the
python-certbot-apache package as well. I was not able to get automated Apache certificate installation to work, so I used the
webroot method instead (which only requires the basic client; no plugins).
Step 3 - Obtain the Certificate
The step of actually getting the certificate from Let’s Encrypt is quite easy, but this is where I depart from most of the guides I’ve found. The automatic Apache plugin method didn’t work for me. This thread on the Let’s Encrypt forums describes my problem exactly, and I didn’t feel like contacting my ISP to fix the issue like the guy in that thread apparently did. So, I tried a few things and what eventually worked best was the
$ sudo certbot certonly --webroot -w /var/www/html/portfolio-jekyll/_site -d peterhenry.net -d www.peterhenry.net
Some breakdown of what this is doing:
certonlyobtains the certificate, but makes no attempt to install it anywhere for you
--webrootspecifies the ownership verification method you want to use
-w /path/to/webrootthe website root of the domain(s) the certificate is for
-d example.coma domain name that the certificate needs to cover (repeat if more domains/subdomains are needed)
If you want more in-depth explaination, examples, and caveats, check out the official webroot documentation (other verification methods are documented on the same page if you want to get an idea of what else is possible).
After you run the command, you should get screens asking to confirm that you’ve read the terms of service and asking for an email that can be used for expiration reminders. Please note that the email on a signed certificate cannot be changed (though you can simply issue a new certificate if you need to change the email). You can of course change the email address stored in the Let’s Encrypt configs used to make new certificates whenever you like.
When you are done, you should get something similar to this success message:
IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at /etc/letsencrypt/live/peterhenry.net/fullchain.pem. Your cert will expire on 2017-01-08. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le
Side Info About How Let’s Encrypt Manages Certificates
This isn’t essential to the installation process so you can skip to Step 4 if if you want. But, if you want to know a little more about how Let’s Encrypt lays things out, read on!
At this point, your
/etc/letsencrypt directory structure should look something like this:
/etc/letsencrypt ├── accounts │ ├── acme-staging.api.letsencrypt.org │ │ └── directory │ │ └── 5a9e0508f2c3571cd1c635d0244deb2a │ └── acme-v01.api.letsencrypt.org │ └── directory │ └── 9c836162b586a440e14996d6ec2fb753 <-- stores owner account information ├── archive │ └── peterhenry.net <-- Stores domain certificate history long term ├── csr ├── keys ├── live │ └── peterhenry.net <-- Stores domain active certificates as symbolic links to archive └── renewal <-- Stores the renewal method of each cert (defaults to creation method)
And the contents of
/etc/letsencrypt/live/peterhenry.net should look like this:
lrwxrwxrwx 1 root root 38 Oct 7 22:54 cert.pem -> ../../archive/peterhenry.net/cert1.pem lrwxrwxrwx 1 root root 39 Oct 7 22:54 chain.pem -> ../../archive/peterhenry.net/chain1.pem lrwxrwxrwx 1 root root 43 Oct 7 22:54 fullchain.pem -> ../../archive/peterhenry.net/fullchain1.pem lrwxrwxrwx 1 root root 41 Oct 7 22:54 privkey.pem -> ../../archive/peterhenry.net/privkey1.pem
Let’s break down what each of these files is:
cert.pemcontains your domain’s certificate: a block of text set off with
chain.pemcontains the intermediate certificate(s) that signed your certificate. In my case, there is a single intermediate (signed by Let’s Encrypt Authority X3) and so there is a single block of text in this file set off with
-----END CERTIFICATE-----blocks just like in
fullchain.pemis quite simply your certificate and the intermediate certificate(s) concatenated together into a single file.
privkey.pemis your private key and should be protected as much as possible: a block of text set off with
-----BEGIN PRIVATE KEY-----and
-----END PRIVATE KEY-----. Never paste this key anywhere online or expose it to the public, and restrict access to it by users on your server as much as possible.
Note that all four files are symbolic links to actual files in the
archive/peterhenry.net directory. This is so that if the certificate expires, the links can be removed from the
live directory but the certificate info will remain in the archive for future reference.
If you are curious how certificate chaining works with Let’s Encrypt, check out their page about the Chain of Trust.
Step 4 - Create Secure and Plaintext VHosts
My old vhost opening tag listened for traffic on port 80, like this:
We need to set up two vhosts - one that will listen for secure traffic on port 443 and one that intercepts plaintext traffic on port 80 and redirects it to the secure port. We can do this in the same
.conf file, no problem:
<VirtualHost *:80> ServerName peterhenry.net Redirect / https://peterhenry.net/ </VirtualHost> <VirtualHost *:443> ServerName peterhenry.net ServerAlias peterhenry.net www.peterhenry.net DocumentRoot "/var/www/html/portfolio-jekyll/_site" ErrorLog "/var/www/html/portfolio-jekyll/error_log" CustomLog "/var/www/html/portfolio-jekyll/access_log" combined <Directory /var/www/html/portfolio-jekyll/_site> AllowOverride All Allow from all Require all granted </Directory> </VirtualHost>
Step 5 - Point the VHost to the Certificate
We need to indicate to Apache where to find your new certificates. There are two ways of doing this, depending on your version of Apache, so find out what version you have with this:
$ apachectl -v
Old Style (Apache Version <= 2.4.8)
Put these lines inside your secure vhost:
SSLCertificateFile /etc/letsencrypt/live/peterhenry.net/cert.pem SSLCertificateKeyFile /etc/letsencrypt/live/peterhenry.net/privkey.pem SSLCertificateChainFile /etc/letsencrypt/live/peterhenry.net/chain.pem
New Style (Apache Version >= 2.4.8)
Put these lines inside your secure vhost:
SSLCertificateFile /etc/letsencrypt/live/peterhenry.net/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/peterhenry.net/privkey.pem
SSLCertificateChainFile was deprecated in 2.4.8, and will not work in versions after it. The directive
SSLCertificateFile was extended to be able to parse files that contain both the domain certificate and the intermediate certificates that make up the chain of trust.
Step 6 - SSL Configuration in the VHost
Put these lines inside your secure vhost:
SSLEngine on # enables SSL SSLProtocol all -SSLv2 -SSLv3 # disables SSL 2 (vulnerable to DROWN) & 3 (vulnerable to POODLE) SSLHonorCipherOrder on # requires ciphers from your suite to be attemped in order SSLCompression off # disables TLS compression (vulnerable to CRIME) SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
My source for the cipher suite string is Mozilla’s SSL Config Generator, using my versions of Apache and OpenSSL and the “Intermediate” preset.
With these settings, I have reasonably good coverage. The config generator cites some oldest compatible clients: Firefox 1, Chrome 1, IE 7, Opera 5, Safari 1, Windows XP IE8, Android 2.3, and Java 7.
It should be noted that unless you have a single certificate covering all subdomains that your IP serves, browsers that do not support SNI will be unable to access subdomains that have their own certificates. This applies to Android versions older than 4, Internet Explorer 8 and older on XP, and Java 6. I’m pretty ok with losing those, however.
Step 7 - Apply Changes and Test Coverage
Run this command to restart Apache and apply the changes you’ve made to config files:
$ apachectl graceful
Using this command instead of something like
apachectl restart or
systemctl restart httpd.service will allow any in-progress requests to complete before shutting the process and restarting, for minimal interruption of service.
I highly recommend submitting your site to Qualys SSL Labs Server Test to check to see if you have misconfigured or missed anything.
I started out with a C grade, but after disabling SSLv3, disabling RC4, referencing my intermediate certificate file (in the right order), disabling SSL compression, and forcing a specific order for my cipher suite - all of which I’ve included in this post - I got up to an A grade. There are a few things I could do to push my numbers higher - OCSP stapling is an easyish one, and I’m sure there are others - but I can’t get an A+ grade in any case until my certificate are at least 6 months old, so I’m good for now.
There is a lot to play with here if you have time.
Step 8 - Set Up Automatic Renewal
Let’s Encrypt certificates expire after 90 days. This limit was designed to be short, in order to encourage people to set up automatic renewal from the very start. This command was included to make automatic renewal as easy as possible:
$ sudo certbot renew
This runs through all certificates you have installed and checks their expiration dates. If a cert will expire in less than 30 days, the command automatically renews it.
By default, it performes the renewal the same way that the certificate was created (i.e.,
apache if you made it with the apache plugin,
webroot if you made it with the webroot method,
standalone if you made it the super basic way, etc).
Open root’s crontab for editing:
$ sudo crontab -e
And put in the following line:
7 3,15 * * * /usr/bin/certbot renew --quiet >> /var/log/le-renew.log
Save and exit. This command runs
/usr/bin/certbot renew --quiet at minute 7 past 3am and 3pm, every day. Any output (which should just be errors, because of the
--quiet flag) is logged to the file
This is according to
certbot’s documentation, which recommends running
certbot renew twice a day at an arbitrary minute in the hour.
Changing the Default Certificate Renewal Method
You can skip this section if you want; if you followed the instructions in the rest of this guide nothing here should be an issue for you. But, if you made a certificate one way and want to renew it another way, keep reading.
I ran into an issue because I created my first certificate using
standalone, which requires ports 80 and 443 to be available in order to renew. This means that Apache can’t be running while the renewal is happening, meaning that just running
certbot renew by itself would fail on this cert (because Apache should always be running).
One fix for this is to write a script that stops Apache, runs
certbot renew and then restarts Apache afterwards, but that’s unnecessary downtime.
Another fix is to renew the certificate with the
webroot method by simply using the same
certbot certonly --webroot command I gave in Step 3. If you run it on an existing certificate, it will give the choice of renewing the cert or reissuing it. The problem is that doing this only renews this particular certificate and you lose all the benefit of the
certbot renew command.
Turns out that the default renewal behavior is defined in
/etc/letsencrypt/renewal/peterhenry.net.conf, which is created at the same time as the certificate. So, I created a new
webroot certificate for a different subdomain and then copied (with modifications) what looked like the important bits of its renewal config to the renewal config of my
standalone cert, like this:
# renew_before_expiry = 30 days version = 0.8.1 cert = /etc/letsencrypt/live/peterhenry.net/cert.pem privkey = /etc/letsencrypt/live/peterhenry.net/privkey.pem chain = /etc/letsencrypt/live/peterhenry.net/chain.pem fullchain = /etc/letsencrypt/live/peterhenry.net/fullchain.pem ## Following block is the old standalone config, commented out ## for future reference if necessary: ## Options used in the renewal process #[renewalparams] #authenticator = standalone #installer = None #account = 9c836162b586a440e14996d6ec2fb753 ## Following block is the new webroot config, copied from ## another certificate's default config with modifications # Options used in the renewal process [renewalparams] authenticator = webroot installer = None account = 9c836162b586a440e14996d6ec2fb753 webroot_path = /var/www/html/portfolio-jekyll/_site, [[webroot_map]] peterhenry.net = /var/www/html/portfolio-jekyll/_site www.peterhenry.net = /var/www/html/portfolio-jekyll/_site
And it worked! Running
certbot renew --dry-run renewed both certificates with the
webroot method as desired.
The syntax used in these config files doesn’t appear to be well-documented, however, so do this at your own risk. It sounds like the Let’s Encrypt team is working on standardizing documentation and behavior here, so hopefully in the future it’ll be easier to set up these sort of defaults or figure out what’s going on.
At this point you should have an SSL-enabled website, with Let’s Encrypt certificates that are set up for automatic renewal and an A grade from Qalys SSL Labs. The next thing to do is probably figure out how to enable SPDY and speed up content delivery a little bit, or keep tweaking the SSL settings to push up the category grades on SSL Labs.