(If you are unfamiliar with the abbreviation “TLS“: it is the successor to SSL but works one the same principle.)
The internet is the best invention since sliced bread but it has become an evil place more than ever. We are dealing with script kiddies, out-of-control secret services and organized crime on a large scale. It is completely out of the question to run any unencrypted services over the internet. I even consider any password immediately wasted that went through the internet in plain text. So we need to create an encryption key and a certificate for Postfix (SMTP), Dovecot (IMAP/POP3) and Roundcube (Webmail/HTTPS).
You may argue that using encrypted connections between a users’s computer and the mail server is futile. After all the email is forwarded to other mail servers in plain text. While this may be true for some mail servers most administrators strive to enable TLS for the server-to-server communication, too. You cannot rely on it for confidential correspondence but it’s better than nothing. Fun fact: not very long ago a couple of large german freemail providers founded an “email made in Germany” alliance. Guess what cool thing they did to make the world a better place… they enabled TLS. 🙂
Caveat: do not use different certificates for Postfix and Dovecot. At least the MacOS mail client would refuse your connection with confusing error messages.
Why makes mail clients trust a certificate?
Just like with your favorite web browser there is a list of “trusted” certificate authorities in the world. Yes, your browser trusts companies you probably have never heard of. And so does your mail client. Decades ago companies managed to convince browser developers to make their browser trust all certificates that were cryptographically signed by them. Those certificates were technically the same as self-signed certificates. They just had a signature that was created by a computer of a certain company. Most of the time there isn’t even a human checking the signing request. I consider today’s certification system broken.
In the previous tutorial for Debian Jessie I had a lengthy comparison of self-signed certificates, company PKIs, LetsEncrypt and paid certificates. Let’s cut it short – we will use LetsEncrypt. There is no reason to pay the certificate mafia money any more. Why do I consider them mafia-like? Because it is plain wrong to exchange money for trust. And the recent history of awkward failures shows that they deserve no trust. Even my preferred good old certificate authority StartSSL (because they issued certificates for free) has failed so badly that all major browsers now consider their certificates untrustworthy. Just recently they even shut down their business.
LetsEncrypt is a not-for-profit project run by a Californian corporation called ISRG (Internet Security Research Group). They managed to set up an infrastructure that finally allows everyone in the world to get trusted certificates for free. Their authority’s certificate is nowadays installed as trusted by most web browsers and other software – like mail clients. If your boss asks… their certificates are technically just as secure as a self-signed certificates. And, no, there is no reason to ever pay for a certificate again. Why do others still do that? Likely because they are victims of marketing and FUD.
The certificate will be used in three places:
- the webmail interface (driven by Apache)
- Postfix (for authentication during SMTP communication with your users)
- Dovecot (for authentication during IMAP communication)
Preparing the Apache web server for HTTP
Let’s start with the Apache. Your server may serve multiple name-based virtual hosts. That just means that your web server can distinguish which files it is supposed to send to the client depending on the host name used in the URL. As an example I will assume that you want to offer a host name webmail.example.org to your users. Of course your server will have another name in a domain that you control. I will use that example throughout the tutorial though and keep that name printed in bold letters to remind you that you have to use your own host name everywhere.
First you need a web root directory for that host name:
Next you will have to create a virtual host configuration file. Apache on Debian uses a neat system to manage virtual hosts:
- /etc/apache2/sites-available/*.conf contains two configuration files by default. “000-default.conf” is a HTTP virtual host and “default-ssl.conf” is a HTTPS virtual host configuration. You can create arbitrary files here. Apache will not consider them automatically.
- /etc/apache2/sites-enabled/*.conf contains symbolic links (“symlinks”) pointing to configuration files in the /etc/apache2/sites-available directory. Only *.conf links in this directory will be loaded by Apache.
This system allows you to enable and disable virtual hosts without having to destroy any configuration. Debian ships with the “a2ensite” (short for “apache2 enable site”) and “a2dissite” commands. In addition to some sanity checks those commands essentially create or remove symlinks between “sites-available” and “sites-enabled”.
(Note: Configuration files must have a “.conf” suffix or else they will get ignored.)
You may remove the default symlinks in /etc/apache2/sites-enabled/* unless you use them already.
Create a new virtual host configuration file /etc/apache2/sites-available/webmail.example.org-http.conf and make it contain:
<VirtualHost *:80> ServerName webmail.example.org DocumentRoot /var/www/webmail.example.org </VirtualHost>
The simple configuration makes Apache handle HTTP requests (on the standard TCP port 80) if a certain line in the request from the browser reads “Host: webmail.example.org”. So the browser actually tells your Apache web server which server name it is looking for. That allows for multiple web sites on a single IP address. (By the way: this works on HTTPS, too, nowadays, thanks to Server Name Indication.)
Enable the site:
You will be told:
To activate the new configuration, you need to run: systemctl reload apache2
Let’s check if the configuration works. Put a test file into your web root directory:
echo "Just a test" > /var/www/webmail.example.org/test
Now when you open the URL http://webmail.example.org/test in your browser you should see the text “Just a test”.
This is enough setup to make LetsEncrypt issue a certificate for you.
Getting a LetsEncrypt certificate
Debian Stretch contains the most common tool to manage LetsEncrypt certificates – certbot. Install it:
apt install certbot
To get a certificate for your domain run:
certbot certonly --webroot --webroot-path /var/www/webmail.example.org -d webmail.example.com
The first time you do that you will get asked for your email address so LetsEncrypt can send you reminders if your certificate would expire. You will also have to agree to their terms of service.
If everything worked well you should get output like:
Saving debug log to /var/log/letsencrypt/letsencrypt.log Obtaining a new certificate Performing the following challenges: http-01 challenge for webmail.example.org Using the webroot path /var/www/webmail.example.org for all unmatched domains. Waiting for verification... Cleaning up challenges Generating key (2048 bits): /etc/letsencrypt/keys/0049_key-certbot.pem Creating CSR: /etc/letsencrypt/csr/0049_csr-certbot.pem IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at /etc/letsencrypt/live/webmail.example.org/fullchain.pem. Your cert will expire on 2018-04-01. 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
Do you wonder what actually happened? Well, the Certbot created the private/secret key (that only your server must know). It then placed a certain file into /var/www/webmail.example.org/.well-known/… and had it verified by the LetsEncrypt service that visited your web site. That was proof enough that you own the domain (because you are in control of the DNS zone) and that you have access to the web server.
Apache and HTTPS
Unencrypted HTTP communication is not an option any more nowadays. And now that you have a certificate let’s add a virtual host that can handle HTTPS.
Create a new file /etc/apache2/sites-available/webmail.example.org-https.conf containing:
<VirtualHost *:443> ServerName webmail.example.org DocumentRoot /var/www/webmail.example.org SSLEngine on SSLCertificateFile /etc/letsencrypt/live/webmail.example.org/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/webmail.example.org/privkey.pem </VirtualHost>
This virtual host configuration looks suspiciously similar to the HTTP virtual host above. It just listens on port 443 (standard port for HTTPS) instead of port 80. It enables the “SSLEngine” that handles encryption and gets information about the certificate for your web server (that is shown to your users) and the private key (that the web servers uses to decrypt the user’s communication).
Enable the SSL module in Apache:
Then enable the virtual host for HTTPS:
And reload the configuration again:
systemctl reload apache2
Now when you point your web browser to https://webmail.example.org your browser should tell you that it trusts the web site’s certificate:
(Yes, sorry, this is not webmail.example.org. But I do not own the example.org domain and thus cannot get a valid certificate for it. This is my own site.)
So should you keep the HTTP virtual host? Yes. Use it to redirect your users to the HTTPS site if they forget to put “https://” in front of the URL. And HTTP is always required for LetsEncrypt. So what is the solution?
Getting a certificate is only half the battle. LetsEncrypt’s certificates expire after 90 days. So renewing certificates automatically and in time is vital. The certbot package adds an automatic cron job in /etc/cron.d/certbot for that purpose. It is run twice a day and after a random delay (so that the LetsEncrypt service is not getting too many requests at the same time) checks if certificates are due for renewal. That is done by calling “certbot -q renew”. The “-q” stands for “quiet” and prevents that you get two emails per day telling you what the certbot did.
There is one puzzle piece missing though. Even when the renewal worked it will only update the certificate files. But the software components – Postfix, Dovecot and Apache – will not notice the change. So we need to add a so called post-hook to certbot that triggers a restart of all processes thereafter.
Create a simple shell script in /usr/local/bin/certbot-post-hook that contains:
#!/bin/sh service postfix restart service dovecot restart service apache2 restart
Make that script executable:
chmod u+x /usr/local/bin/certbot-post-hook
Run it as a test:
It will not output anything. It should just run without error and restart all three services that will use the certificate. You will need to add that script by running “certbot renew” with the option “–post-hook /usr/local/bin/certbot-post-hook”.
Now it depends on whether the renewal is triggered by cron or by systemd. If you are using Debian Jessie or Debian Stretch your system uses systemd. Certbot handles both cases but there is a caveat that may make add the mentioned option in the wrong place. Check out the /etc/cron.d/certbot file:
0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(3600))' && certbot -q renew
The highlighted part means that the cron job checks if you are using systemd. If you do it would not run the renewal here and leave the work for systemd. In this case you may want to edit the /lib/systemd/system/certbot.service file instead:
[Unit] Description=Certbot Documentation=file:///usr/share/doc/python-certbot-doc/html/index.html Documentation=https://letsencrypt.readthedocs.io/en/latest/ [Service] Type=oneshot ExecStart=/usr/bin/certbot -q renew PrivateTmp=true
You may be tempted to simply change the ExecStart line here. But with the next update of certbot your changes will be overwritten. Instead copy that file to /etc/systemd/system/
cp /lib/systemd/system/certbot.service /etc/systemd/system/
Change the ExecStart line to:
ExecStart=/usr/bin/certbot -q --post-hook /usr/local/bin/certbot-post-hook renew
Now reload systemd:
You can also verify your overriden service using:
systemctl cat certbot
To be sure that the renewal will work you can simulate the process by running
certbot --dry-run --post-hook /usr/local/bin/certbot-post-hook renew
Well done. You have implemented LetsEncrypt for all your services now. Let’s go on.