HTTPS / SSL with letsencrypt / certbot
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
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
or for nginx
apt-get install python-letsencrypt-nginx
After this, you have the choice to try to resolve everything automatically by using a plugin-based auto installation:
letsencrypt --apache
or
letsencrypt --nginx
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
or
letsencrypt --nginx 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>
A basic config for nginx would look something like this:
server {
listen 443 default_server;
listen [::]:443 default_server ipv6only=on;
server_name mydomain.com;
ssl on;
ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;
root /var/www/mydomain;
index index.html;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name mydomain.com;
return 301 https://$server_name$request_uri;
}
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