Create a gist now

Instantly share code, notes, and snippets.

Embed

Let's Encrypt with nginx + auto renewal

Let's Encrypt is a new, free, automated, and open Certificate Authority.

Find out more out Let's Encrypt, and how you can use in the Let's Encrypt documentation.

This file will guide you through the minimum things you need to set up Let's Encrypt on your nginx webserver.

Before we get started we need to install the letsencrypt/certbot CLI tool. You can follow the installation instruction over at Certbot.

Setting up nginx

Temporary webroot / acme-challenge

Before we configure our vhosts we want to set up some default configurations. Start out creating a temporary webroot:

$ mkdir -p /usr/local/etc/nginx/letsencrypt

Next up we need to create this common router for our domains. This will point to the /.well-known/acme-challenge/ folder in our webroot to confirm the domains. Let's create the file /usr/local/etc/nginx/acmechallenge.conf:

# letsencrypt acme challenge for domain verification
location '/.well-known/acme-challenge/' {
  root /usr/local/etc/nginx/letsencrypt;
}

# redirect all traffic to http -> https
location / {
  return 301 https://$server_name$request_uri?;
}

nginx config

Create a confirugation file for your vhost/domain in the nginx.conf (or a sepparate file you include):

server {
  listen 80;
  listen [::]:80;
  server_name mydomain.com www.mydomain.com;

  include /usr/local/etc/ningx/acmechallenge.conf; # the config created in the step above
}

... then reload nginx:

$ service nginx reload

Now your server will redirect you to the https version every time you vists modomain.com.

Request certificate

This can be done in the CLI, but we're using a config file instead. Check out the documentation for more information on how to set up the configuration file.

Let's create mydomain.ini:

rsa-key-size = 4096
email = my@domain.com
domains = mydomain.com, www.mydomain.com
text = True
authenticator = webroot
webroot-path = /tmp/letsencrypt/

Run certbot to obtain the certificates:

$ certbot certonly -c mydomain.ini

(on some systems the tool would be named letsencrypt. I.e. FreeBSD).

Update the nginx config

Now we need to add a new server section to our vhost configuration who listen to port 443 (https):

server {
  listen 80;
  listen [::]:80;
  server_name mydomain.com www.mydomain.com;

  include /usr/local/etc/ningx/acmechallenge.conf;
}

server {
  listen 443;
  listen [::]:443;
  server_name mydomain.com, www.mydomain.com;

  ssl on;
  ssl_stapling on;
  ssl_stapling_verify on;

  ## These files are the certificates we just requested
  ssl_certificate           /usr/local/etc/letsencrypt/live/mydomain.com/fullchain.pem;
  ssl_certificate_key       /usr/local/etc/letsencrypt/live/mydomain.com/privkey.pem;
  ssl_trusted_certificate   /usr/local/etc/letsencrypt/live/mydomain.com/chain.pem;

  location / {
    root /usr/local/var/www/;
  }
}

Reload nginx:

$ service nginx reload

and you're good to go!

Automatic renewal

Your Let's Encrypt certificates will expire after 3 month. It's recommended to renew them every 2 month. To do this we set up crontab to run the renew command every 2 month:

0 0 1 1,3,5,7,9,11 1 /path/to/certbot renew --quiet
5 0 1 1,3,5,7,9,11 1 service nginx reload

Get even more secure!

To further increase security, you should generate a strong Diffie-Hellman group. This command will generate a 4096-bit group:

$ openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096

To allow only the most secure SSL protocols and ciphers, and use the strong Diffie-Hellman group we generated, add the following lines to the server block:

add_header Strict-Transport-Security 'max-age=15552000; preload';
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

gzip off;

ssl_dhparam /etc/ssl/certs/dhparam.pem; ## the diffie hellman group we generated

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
ssl_prefer_server_ciphers on;

ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;

ssl_stapling on;
ssl_stapling_verify on;

gzip

Compression over SSL will open up some bad stuff. gzip is therefore deactivated.

TLS

Deactivating TLSv1 and TLSv1.1 will give best security. But will leave out a lot of users whom still using older software.

Extras

See the nginx ssl documentation for https support.

The recommended cipher suite is*:

ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';

The recommended suite for backward compatibility(WinXP) is:

ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";

Run the command openssl ciphers to see the full list of ciphers.

You can simulate the handshake at SSL Labs.

Others

Check out these posts for an overview on nginx' security:

@markusra

This comment has been minimized.

Show comment
Hide comment
@markusra

markusra Jul 26, 2016

Your Let's Encrypt certificates will expire after 3 month. It's recommended to renew them every 2 month. To do this we set up crontab to run the renew command every 2 month.

As I can see, there is one problem with your crontab. What if there are issues with the Let's Encrypt servers or your own server on this specific day? Shouldn't you fire this command every day? certbot renew only attempts to renew any previously-obtained certificates that expire in less than 30 days (https://certbot.eff.org/docs/using.html#renewal).

markusra commented Jul 26, 2016

Your Let's Encrypt certificates will expire after 3 month. It's recommended to renew them every 2 month. To do this we set up crontab to run the renew command every 2 month.

As I can see, there is one problem with your crontab. What if there are issues with the Let's Encrypt servers or your own server on this specific day? Shouldn't you fire this command every day? certbot renew only attempts to renew any previously-obtained certificates that expire in less than 30 days (https://certbot.eff.org/docs/using.html#renewal).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment