How to use Let’s Encrypt certificate with Nginx

I am using Let’s Encrypt certificates for several services with great success. It is easy, reliable and very straightforward service. I will share with you my personal setup used to secure AWStats statistics page as a simple example.

Step 1

Create letsencrypt system user using /srv/letsencrypt directory to store relevant data.

$ sudo useradd --system --create-home --home-dir /srv/letsencrypt --shell /bin/bash letsencrypt

Create ssl directory to store certificate files.

$ sudo mkdir -p /srv/letsencrypt/ssl

Deny other users except www-data from accessing this directory.

$ sudo chown letsencrypt:www-data /srv/letsencrypt/ssl
$ sudo chmod 750 /srv/letsencrypt/ssl

Step 2

Switch to letsencrypt user and change working directory to /srv/letsencrypt.

$ sudo -u letsencrypt -i

Download acme-tiny utility.

acme-tiny is a tiny script to issue and renew Let's Encrypt certificates. It is a way better than original client, so you should definitely give it a try.
letsencrypt@debian:~$ git clone https://github.com/diafygi/acme-tiny.git
Cloning into 'acme-tiny'...
remote: Counting objects: 232, done.
remote: Total 232 (delta 0), reused 0 (delta 0), pack-reused 232
Receiving objects: 100% (232/232), 46.93 KiB | 0 bytes/s, done.
Resolving deltas: 100% (124/124), done.
Checking connectivity... done.

Create acme-challenge directory to host challenge files.

letsencrypt@debian:~$ mkdir acme-challenge

Generate Let's Encrypt account private key.

letsencrypt@debian:~$ openssl genrsa 4096 > account.key

Secure generated key.

letsencrypt@debian:~$ chmod 400 account.key

Step 3

Configure nginx web-server by adding the following /.well-known/acme-challenge/ location block to the virtual host listening on port 80 configuration you want to secure.

Remember to allow server itself, as the script will verify that the challenge file is in place.
server {
  listen      80;
  listen [::]:80;
  server_name statistics.sleeplessbeastie.eu;

  location /.well-known/acme-challenge/ {
    allow 111.222.111.99/32;              # allow self IPv4
    allow 1a01:2a02::3a03:4a04:5a05:6a06; # allow self IPv6
    allow 66.133.109.36/32;               # allow outbound1.letsencrypt.org
    allow 64.78.149.164/32;               # allow outbound2.letsencrypt.org
    deny all;                             # deny everything else

    alias /srv/letsencrypt/acme-challenge/;
    try_files $uri =404;
  }
  location / {
    return 301 https://$host$request_uri;
  }
}

Reload nginx configuration.

$ sudo systemctl reload nginx

Step 4

Download intermediate certificate.

letsencrypt@debian:~$ curl --output ssl/intermediate.crt --silent https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem

Generate domain private key for sleeplessbeastie.eu domain.

letsencrypt@debian:~$ openssl genrsa 4096 > ssl/sleeplessbeastie.eu.key

Generate certificate signing request for statistics.sleeplessbeastie.eu domain.

letsencrypt@debian:~$ openssl req -new -sha256 -key ssl/sleeplessbeastie.eu.key -subj "/CN=statistics.sleeplessbeastie.eu" > ssl/statistics.sleeplessbeastie.eu.csr

Request and store certificate.

letsencrypt@debian:~$ python acme-tiny/acme_tiny.py --account-key ./account.key --csr ./ssl/statistics.sleeplessbeastie.eu.csr --acme-dir acme-challenge/ > ./ssl/statistics.sleeplessbeastie.eu.crt
Parsing account key...
Parsing CSR...
Registering account...
Already registered!
Verifying statistics.sleeplessbeastie.eu...
statistics.sleeplessbeastie.eu verified!
Signing certificate...
Certificate signed!

Concatenate intermediate certificate with the signed one.

letsencrypt@debian:~$ cat ssl/statistics.sleeplessbeastie.eu.crt ssl/intermediate.crt > ssl/statistics.sleeplessbeastie.eu_chained.crt

Step 5

Configure nginx web-server by specifying ssl certificate and private key inside the secured virtual host configuration.

You can test your site's configuration and certificate using Qualys SSL Labs SSL Server Test. You should get overall rating A.
server {
  listen      443 ssl http2;
  listen [::]:443 ssl http2;
  server_name statistics.sleeplessbeastie.eu;

  ssl on;
  ssl_certificate_key /srv/letsencrypt/ssl/sleeplessbeastie.eu.key;
  ssl_certificate     /srv/letsencrypt/ssl/statistics.sleeplessbeastie.eu_chained.crt;
  ssl_dhparam         dhparams.pem;

  ssl_ciphers AESGCM:HIGH:!aNULL:!MD5;
  ssl_prefer_server_ciphers On;

  [...]
}
Use dhparam -out dhparam.pem 4096 command to generate Diffie-Hellman parameter file.

Reload nginx configuration.

$ sudo systemctl reload nginx

Step 6

Use the following code to automatically renew certificate on the first day of the month 23:30 hour using crontab.

$ cat << EOF | sudo tee -a /etc/cron.d/letsencrypt   
30 23 01 * *   root    sudo -u letsencrypt python /srv/letsencrypt/acme-tiny/acme_tiny.py --account-key /srv/letsencrypt/account.key --csr /srv/letsencrypt/ssl/statistics.sleeplessbeastie.eu.csr --acme-dir /srv/letsencrypt/acme-challenge/ > /srv/letsencrypt/ssl/statistics.sleeplessbeastie.eu.crt && cat /srv/letsencrypt/ssl/statistics.sleeplessbeastie.eu.crt /srv/letsencrypt/ssl/intermediate.crt > /srv/letsencrypt/ssl/statistics.sleeplessbeastie.eu_chained.crt && systemctl reload nginx
EOF
nginx configuration can be easily simplified by using include directive to apply dynamically generated code.

Additional notes

Do not forget to test nginx configuration before reloading it.

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Milosz Galazka's Picture

About Milosz Galazka

Milosz is a Linux Foundation Certified Engineer working for a successful Polish company as a system administrator and a long time supporter of Free Software Foundation and Debian operating system. He is also open for new opportunities and challenges.

Gdansk, Poland https://sleeplessbeastie.eu