HTTPS / SSL with letsencrypt / certbot

2017-03-11 | #helper, #server, #ubuntu

HTTPS keeps getting more and more important as the default way to deliver basically any professional website, especially since companies like Google started allowing geolocation only on SSL-enabled sites.

Since the letsencrypt initiative (recently renamed to certbot) we finally have a solution for private websites, without the need to buy any commercial certificate. Instead you generate a 90-day certificate at letsencrypt and renew that certificate regularily through a cron job.

The downside to this approach is the server setup, but thanks to the CLI-tools on offer, that's not even a real problem for admin noobs like myself.

So, to start of, visit

https://certbot.eff.org/

and select the system on which you'll want to install.

In my case this was an Apache2 on Ubuntu 16.04 Xenial, which would result in an

apt-get install python-letsencrypt-apache

After this, you have the choice to try to resolve everything automatically by using a plugin-based auto installation:

letsencrypt --apache

This starts an interactive dialogue, parsing your hosts and writing the necessary config into those. This is a very good choice for simple configurations.

However, if you have a config a little bit more difficult, especially with multiple hosts, subdomains and if you want to have HTTPS as your default way of serving (and not separate, duplicated hosts for ports 80 and 443) you'll have to put in some manual steps.

You can tell letsencrypt to keep its hands off your confs by using adding "certonly" to the command. Additionally, you can define further subdomains through a parameter. Why the tool resolves ServerNames, but no ServerAliases without wildcards is beyond me, but so be it. Make sure to have all supplied subdomains defined as ServerAlias in a conf (it's very important to set the alias in the *:80-host as well as the *:443-host!).

letsencrypt --apache certonly -d mydomain.com,www.mydomain.com,subdomain.mydomain.com

If you are not sure how such an Apache2-vhost might look like in the flesh, have a look at this conf, with automatic enabling of HTTPS, subdomains and some further stuff like gzipping and wsgi (which you can leave out if you are not using WSGI).

By the way: If you are using WSGI and have a WSGIProcessGroup defined, the Apache-autoconfig-script will fail, because after duplicating your host, there will be two places the group is defined, which is a syntax error. To circumvent this, comment the line out before proceeding, and after the new configs are ready, just uncomment it in the *:80-host.

But now for the config:

WSGIRestrictStdout Off

<VirtualHost *:443>

ServerName mydomain.com

ServerAlias www.mydomain.com

ServerAdmin admin@mydomain.com

DocumentRoot /var/www/mydomain.com/htdocs

Alias /favicon.ico /var/www/mydomain.com/htdocs/favicon.ico

<Directory />

Options FollowSymLinks

AllowOverride None

</Directory>

<Directory /var/www/mydomain.com/htdocs>

Options Indexes FollowSymLinks MultiViews

AllowOverride All

Order allow,deny

Allow from all

</Directory>

Alias /site/static/ /var/www/mydomain.com/static/

<Directory /var/www/mydomain.com/static>

Order deny,allow

Allow from all

</Directory>

Alias /site/media/ /var/www/mydomain.com/media/

<Directory /var/www/mydomain.com/media>

Order deny,allow

Allow from all

</Directory>

WSGIDaemonProcess mydomain.com

WSGIProcessGroup mydomain.com

WSGIScriptAlias /site /var/www/mydomain.com/wsgi.py

SetOutputFilter DEFLATE

SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|ico|png)$ no-gzip dont-vary

SetEnvIfNoCase Request_URI \.(?:exe|t?gz|zip|bz2|sit|rar)$ no-gzip dont-vary

SetEnvIfNoCase Request_URI \.pdf$ no-gzip dont-vary

BrowserMatch ^Mozilla/4 gzip-only-text/html

BrowserMatch ^Mozilla/4\.0[678] no-gzip

BrowserMatch \bMSIE !no-gzip !gzip-only-text/html

LogLevel warn

ErrorLog ${APACHE_LOG_DIR}/error.log

CustomLog ${APACHE_LOG_DIR}/access.log combined

SSLCertificateFile /etc/letsencrypt/live/mydomain.com/fullchain.pem

SSLCertificateKeyFile /etc/letsencrypt/live/mydomain.com/privkey.pem

Include /etc/letsencrypt/options-ssl-apache.conf

</VirtualHost>

<VirtualHost *:80>

ServerName mydomain.com

ServerAlias www.mydomain.com

ServerAdmin admin@mydomain.com

DocumentRoot /var/www/mydomain.com/htdocs

RewriteEngine on

RewriteCond %{HTTPS} off

RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

</VirtualHost>

Check your config before generating a certificate and proceeding with

apachectl configtest

or whatever your webserver offers in that regard.

After the confs are set up and the certificate has been generated for your domains and been placed in "/etc/letsencrypt", the last thing you should do is to set up a cronjob, which automatically renews your certificates before they expire. Letsencrypt suggests to run their renew script twice a day.

To check if a renewal would work beforehand, try the following two commands in your server shell:

letsencrypt renew --dry-run --agree-tos

letsencrypt renew

If those two succeed (sometimes a warning about a missing email address appears, ignore that) you may install the cronjob via "crontab -e" and add the following line:

16 0,12 * * * letsencrypt renew

This states, that the renewal is triggered at 0.16h and 12.16h. You should set the minutes to another random value to spread the request load, maybe even think about choosing other hours.

After this your setup should be finished and you should get your pages via HTTPS with a valid certificate, no matter with what protocol you entered the page.

PS: On newer packages the bin is renamed to certbot, just replace that in all commands