Deploying Let’s Encrypt in production

With Nginx on Debian/Linux

Enabling TLS/SSL encryption for a customer’s website used to be a tedious process:

  • Convince my customer that it’s a good idea to encrypt the website.
  • Buy a certificate. Not a big deal for 8$ at namecheap, but still.
  • Have the certificate issued. It sucks. That’s why I had my customers pay for my time which in turn explains the need to convince him.

It sucked?

These were my steps that were required to secure a website with TLS/SSL:

  1. Create a key pair. Sure, the command was, remember:
    openssl req -new -nodes -keyout myapp.key -out myapp.csr -newkey rsa:2048
  2. Create a certificate signing request (CRS)
  3. Submit the CSR to your certificate-issuing authority. On their website, after logging in.
  4. Make sure, that hostmaster@yourdomain.com is a working mailbox or at least forwards emails to you.
  5. Wait for the authority to send you an email that contains a link or a password you have to enter in some kind of form.
  6. Wait another time for the authority to send you the certificate (and hopefully all required intermediate certificates) as a zip file
  7. Transfer that zip file to your server
  8. Configure your web server to use the certificate and the private key from step 1 to enable TLS/SSL.

One hour gone. At least.

For me, handling the emails is most annoying part, as this often requires asking the person who’s accountable for the company’s email server to create the account. Or fumble around with email administration tools of whatever domain registrar my customer chose. Or — worst — create a mailbox yourself, configure the corresponding DNS MX entries for the given domain. And wait for the DNS changes to propagate. Another hour gone.

To the rescue

Basically, Let’s Encrypt works like this: You run a client-software from available from Let’s Encrypt. You give it a domain name you’d like to get a TLS/SSL certificate for. The infracstructure of Let’s Encrypt verifies that you have control over this domain and issues a certificate for you.

The client software, a nifty command line tool, will automatically create a key pair for you and will fetch the newly-issued certificate from Let’s Encrypt’s servers. It will also concatenate all required intermediate certificates in the correct order. All in all, this saves a lot of time.

And it’s free! :-)

Downsides and limitations

Let’s Encrypt issues certificates that are valid for only three months. So it’s on your behalf to implement a strategy — or better some kind of automation — that renews your certificates.

Certificates are domain-validated only and marked as such. If you need identity-validated certificates, Let’s Encrypt is not for you.

How I do it with on Debian with Nginx

All my or my client’s servers run Debian 8/Jessie. I am using the dotdeb repository for a newer Nginx Version 1.8.1 instead of 1.6.1 that ships with the pagespeed module, but instructions work with Debian’s standard Nginx 1.6.1 as well.

OK, let’s get finally going. All commands are run as superuser (root). Install the client to your home directory:

git clone https://github.com/letsencrypt/letsencrypt

Alter you site’s Nginx config. Add the following location rule to your app’s server directive. This should be in /etc/nginx/sites-enabled/yoursite

server {
listen 80;
listen [::]:80; #IPv6
server_name yourapp.com;
...
location /.well-known {
# or some other directory
root /var/www/yourapp/root;
}
}

Reload nginx

root@server: ~$> systemctl reload nginx

Run the Let’s Encrypt client

root@server: ~$> /root/letsencrypt/letsencrypt-auto certonly --non-interactive --agree-tos --email you@whatever.com --domain yourapp.com --renew-by-default --webroot -w /var/www/yourapp/root/

The command line options explained

  • non-interactive: no UI, just CLI please.
  • certonly: I just want the certificate
  • agree-tos: I agree with their terms of service. If I didn’t add this parameter, there would be a screen popping up asking me to. And this would be bad for automation
  • email: email address they’ll send you warnings and such. Doesn’t need to be attached to yourapp.com in any way. Usually just your business email.
  • domain: The domain to secure. In this article, I’ll use yourapp.com as an example domain. Can be stated multiple times. E.g. -d yourapp.com -d www.yourapp.com
  • renew-by-default: create or renews a certificate and doesn’t ask questions on that. This essentially opens a window for another try next month if one automatic renewal goes wrong (learn about automation later).
  • webroot: Tell the program to use a cookie placed in your existing webfolder instead of creating a webservice listening on port 80 and 443. The latter would require me shut shut down Nginx for a few seconds on every certificate request. Which I don’t like.
  • w: where to put that cookie mentioned above

If every thing worked fine, you should see something like

Requesting root privileges to run letsencrypt…
/root/.local/share/letsencrypt/bin/letsencrypt --no-self-upgrade certonly --email you@whatever.com --renew-by-default --webroot -w /var/www/yourapp/root/
IMPORTANT NOTES:
— Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/yourapp.com/fullchain.pem. Your cert
will expire on 2016–05–17. To obtain a new version of the
certificate in the future, simply run Let’s Encrypt again.
— If you like Let’s Encrypt, please consider supporting our work by:
Donating to ISRG / Let’s Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le

Hell Yeah!

Looking at /etc/letsencrypt/live/yourapp.com/ I can see both private key and certificate. This single command just saved me from steps 1–7!

Enabling TLS/SSL

Let’s move on and enable TLS/SSL in your website. Edit your website’s Nginx configuration file. Move all other directives apart from listen:80 and location /.well-known to a new server directive that is ssl-enabled:

server {
listen 80;
listen [::]:80; #IPv6
server_name yourapp.com;
location / {
return https://$host$request_uri;
}
location /.well-known {
# or some other directory
root /var/www/yourapp/root;
}
}
server {
listen 443 ssl spdy;
listen [::]:443 ssl spdy;
ssl on;
ssl_certificate /etc/letsencrypt/live/yourapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourapp.com/privkey.pem;
... # ssl security stripped off, see "Not yet done"
... # your app's directives go here
}

Reload nginx again.

root@server: ~$> systemctl reload nginx

Point your brwoser at http://yourapp.com and you should be redirected to https://yourapp.com (note the https). And you should be done.

Ahh.. Not done, yet.

Two more things. First, we need to renew our certificate on a schedule as it’s only valid for three months. Simply create a cronjob for the issuing command:

root@server: ~$> echo "#!/bin/bash" > /etc/cron.monthly/renew-yourapp-cert
root@server: ~$> echo "/root/letsencrypt/letsencrypt-auto certonly -n --agree-tos --email you@whatever.com -d yourapp.com --renew-by-default --webroot -w /var/www/yourapp/root/" >> /etc/cron.monthly/renew-yourapp-cert
root@server: ~$> echo "systemctl reload nginx" >> /etc/cron.monthly/renew-yourapp-cert
root@server: ~$> chmod ug+x /etc/cron.monthly/renew-yourapp-cert

Now, the certificate should be renewed once a month. Note that nginx needs to be reloaded in order to use the new certificate.

Shameless ad: If you need a small script that checks whether your certificates are still valid, see my github project “certwarner”. It’s written in Perl, though ;-)

Second, please add these lines to your ssl config after “# ssl security stripped off” to disable insecure encryption modes:

ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
ssl_ciphers EECDH+AESGCM:EDH+AESGCM:EECDH:EDH:!MD5:!RC4:!LOW:!MEDIUM:!CAMELLIA:!ECDSA:!DES:!DSS:!3DES:!NULL;
ssl_prefer_server_ciphers on;
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

Summary

The infrastructure (client/server) of Let’s Encrypt let’s me benefit from them twice:

  1. I can automate the key pair and certificate issuing process. This saves me time and hassle.
  2. Certficates can get renewed automatically.
  3. Certificates are free

The result is that I usually don’t need to think about whether a website needs to be encrypted or not. I just use Let’s Encrypt and a small Nginx configuration snippet and I am done.

In production I use my “certwarner” script which reports to DataDog which in turn sends nice error messages when something in the renewing process goes wrong.

Feedback and/or “you’re doing it wrong” welcome.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.