Setting up HTTPS with Let's Encrypt and Apache 2.4 on a systemd-based Linux system including certificate autorenewal

This is a quick guide for setting up Apache 2.4 on a Linux distribution running systemd (such as Debian Jessie and newer) to use Let's Encrypt with automatic renewal of certificates in order to provide HTTPS web hosting. After performing these steps, you should be running a HTTPS Apache web server virtual host and using Let's Encrypt with EFF's Certbot in web root mode for certificate issuance and automatic renewal.

As with anything relating to security, especially anything that needs to be executed as root, do not follow these steps blindly. If any of this makes you ask yourself what it does, review the relevant documentation before proceeding.

Install Certbot

This part is very distribution-specific. The below describes how to install Certbot on Debian Jessie; other distributions are almost certainly going to be different, and other versions are very likely to be different at least in details.

For Debian Jessie, Certbot is available in the jessie-backports repository. Edit /etc/apt/sources.list, or create a new file in /etc/apt/sources.list.d, to pull in at least the "main" section of the jessie-backports repository:

deb http://ftp.debian.org/debian/ jessie-backports main
deb-src http://ftp.debian.org/debian/ jessie-backports main

Update the package lists:

apt-get update

Then install Certbot for Apache:

apt-get install python-certbot-apache --target-release jessie-backports --show-upgraded

Configure a HTTP virtual host for the host name

Web root mode uses HTTP to confirm that the host is under the control of the entity requesting the certificate, both in case of new as well as renewing certificates. Add a virtual host such as the following:

<VirtualHost *:80>
ServerName secure.example.com
DocumentRoot /srv/www/secure.example.com/http/htdocs
RewriteEngine on
RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge(/[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-]*)?$
RewriteRule  ^/(.*)  https://%{HTTP_HOST}/$1  [last,redirect=301]
</VirtualHost>

This web host will serve content for http://secure.example.com/ from /srv/www/secure.example.com/http/htdocs (make sure to create that directory, and make sure it is at least executable to the user that Apache runs as). It will rewrite all requests to go to the corresponding location on https://secure.example.com/ instead, except for any ACME HTTP validation requests as described by draft-ietf-acme-acme-04 and RFC 4648 which are served by this virtual host instead. Redirection is done using HTTP 301 Moved Permanently as defined by RFC 7231; for testing, you may want to use HTTP 307 Temporary Redirect instead.

For the redirection to work, you must load mod_rewrite into your Apache instance. If you really don't want to do that, omit the RewriteEngine, RewriteCond and RewriteRule configuration statements above. If you are using some other module for request rewriting, you are on your own for how to configure it to do the same thing.

Create the directory that the virtual host serves content out of. In our case:

mkdir --parents /srv/www/secure.example.com/http/htdocs

Check to make sure that Apache is happy about the configuration, and that the virtual host is loaded properly:

apache2ctl configtest
apache2ctl -S | less

If either of these commands indicate that something is not working as intended, or that the newly created virtual host is not being loaded as expected, determine what needs to be done to fix that, and fix the problem before proceeding. Then load the configuration into the running Apache instance:

apache2ctl graceful

Obtain the initial certificate

To obtain an initial certificate for the host name in question, run Certbot once in web root mode:

certbot certonly --webroot -w /srv/www/secure.example.com/http/htdocs -d secure.example.com

This tells Certbot to obtain a certificate but not install it, in web root mode, and that on our system, the root directory of http://secure.example.com corresponds to /srv/www/secure.example.com/http/htdocs.

You can, at your option, add --register-unsafely-without-email or --email youremail@example.com, and --rsa-key-size X. The default key size is currently 2048 bits, which should be sufficient for most use cases. Do not use a smaller key size unless you have a very good reason to. The current maximum size is 4096 bits.

Assuming that all goes well, this will create a temporary file in /srv/www/secure.example.com/http/htdocs/.well-known/acme-challenge, and eventually store the files relating to the certificate under /etc/letsencrypt with symbolic links from /etc/letsencrypt/live/secure.example.com. If something goes wrong, it will tell you what went wrong and ideally what you need to do to fix it.

Set up the corresponding HTTPS virtual host

You can now set up a virtual host to serve content over HTTPS. The bare minimum meaningful virtual host configuration with HTTPS enabled would be something along the lines of:

<VirtualHost *:443>
ServerName secure.example.com
DocumentRoot /srv/www/secure.example.com/https/htdocs
SSLEngine on
SSLCertificateKeyFile /etc/letsencrypt/live/secure.example.com/privkey.pem
SSLCertificateFile /etc/letsencrypt/live/secure.example.com/cert.pem
SSLCertificateChainFile /etc/letsencrypt/live/secure.example.com/chain.pem
</VirtualHost>

There are many options that you can add to this such as specifying the protocols or cipher suites, enabling OCSP stapling, enabling HSTS, requiring HTTPS for the virtual host, and so on. Which of those settings to use will depend on your requirements, but none of them are required for a basic functional HTTPS virtual host, and none are required specifically because of Let's Encrypt.

Create the document root directory:

mkdir --parents /srv/www/secure.example.com/https/htdocs

Check your Apache configuration to make sure that the HTTPS virtual host is recognized, and that there are no syntax errors or misspelled paths:

apache2ctl configtest
apache2ctl -S | less

If either of these commands indicate that something is not working as intended, or that the newly created virtual host is not being loaded as expected, determine what needs to be done to fix that, and fix the problem before proceeding. Then load the configuration into the running Apache instance:

apache2ctl graceful

If all went well, by pointing your browser at http://secure.example.com/ you should be redirected to https://secure.example.com/ and the browser should not be complaining about the HTTPS configuration or certificate. Congratulations! You just set up your first HTTPS virtual host with Let's Encrypt and Certbot.

Automatic renewal of certificates

Let's Encrypt certificates are valid only for 90 days, compared to the 1-3 years common for paid certificates. This is done to encourage automation as well as reduce the damage from a key compromise. The next step is to set up the system such that the certificates are automatically renewed before they expire, and automatically loaded into the web server. The latter is required because Apache only reads the certificate files on web server startup.

The specifics will depend on your distribution, but you can expect to find a .service file under /lib/systemd/system which describes how Certbot should be executed, along with a corresponding .timer file that describes when Certbot should be executed. You shouldn't need to touch the .timer file, but it's still a good idea to look at it to make sure the execution interval settings are sane. The .service file, however, may need a bit of tweaking. On Debian Jessie, this is /lib/systemd/system/certbot.service. Open that file in an editor; it should look very similar to the below snippet:

[Unit]
Description=Certbot
Documentation=file:///usr/share/doc/python-certbot-doc/html/index.html
Documentation=https://letsencrypt.readthedocs.io/en/latest/
[Service]
Type=oneshot
ExecStartPre=/usr/bin/perl -e 'sleep int(rand(3600))'
ExecStart=/usr/bin/certbot -q renew
PrivateTmp=true

That's all well and good for what it does, but because we are using web root mode, we need to add loading the certificate into the web server. For Apache, this is most easily done by executing apache2ctl graceful to reload the web server configuration. To make sure we don't unnecessarily break something, we will also run a configtest first; that will exit with an error code if there is a problem with the web server configuration, causing systemd to not run the subsequent command and marking the unit as failed. Add the following two lines between ExecStart= and PrivateTmp=:

ExecStartPost=/usr/sbin/apache2ctl configtest
ExecStartPost=/usr/sbin/apache2ctl graceful

If your Apache web server is installed somewhere other than /usr (for example, /opt or /usr/local), adjust the path as required. You can find a description of ExecStartPre, ExecStart and ExecStartPost in the systemd documentation.

To reload the systemd configuration from disk and thus activate the changes, execute:

systemctl daemon-reload

If there are no errors, your system should now be set up to automatically renew and reload certificates when needed.

Congratulations, you are done! HTTPS away.