Let’s Encrypt & Certbot

Let’s Encrypt is a free, automated, and open Certificate Authority. And Certbot is the shell client to deploy Let’s Encrypt certificates. Some providers manage the certificates for you but in our case we will directly use Certbot.

Installation

Just follow the instruction on Certbot website.

With debian 9 (stretch), we simply do:

$ apt-get install certbot

Or with debian 8 (jessie):

$ apt-get install certbot -t jessie-backports

HTTP Nginx configuration

Before starting to generate our first certificates with Let’s Encrypt, we first need to have Nginx responding to our domain(s). With this example we have our server listening to myapp.com and www.myapp.com domain and subdomain in HTTP with /home/deploy/myapp/current/public folder as root.

server {
  listen 80;
	
  server_name myapp.com www.myapp.com;
  root        /home/deploy/myapp/current/public;
}

And we also need to create the directory if it’s not already existing.

$ mkdir -p /home/deploy/myapp/current/public

Certificates generation

Now to generate certificates we simply execute the following command. You only need to apply it once (or more if you want to add subdomains to the same certificate or change some configuration). By default Certbot comes with a cron which renews certificates by itself.

$ certbot certonly --webroot \
	--webroot-path /home/deploy/myapp/current/public \
	--keep-until-expiring \
	--email email@domain.tld \
	--agree-tos \
	--non-interactive \
	-d domain.tld \
	-d www.domain.tld \
	--rsa-key-size 4096 \
	--post-hook "/bin/systemctl reload nginx.service"
	[--expand]

If you already generate certificates and you want to add one or more new domains, you have to use the --expand option.

Options explanation

Option Description
--webroot Obtain certs by placing files in a webroot directory.
--webroot-path public_html / webroot path. This can be specified multiple times to handle different domains; each domain will have the webroot path that precede it.
--keep-until-expiring If the requested cert matches an existing cert, always keep the existing one until it is due for renewal (for the ‘run’ subcommand this means reinstall the existing cert). (default: Ask)
--email Email used for registration and recovery contact. (default: Ask)
--agree-tos Agree to the ACME Subscriber Agreement (default: Ask)
--non-interactive Run without ever asking for user input. This may require additional command line flags; the client will try to explain which ones are required if it finds one missing (default: False)
 --domain|-d Domain names to apply. For multiple domains you can use multiple -d flags or enter a comma separated list of domains as a parameter.
--rsa-key-size Size of the RSA key. (default: 2048)
--post-hook Command to be run in a shell after attempting to obtain/renew certificates. We use it to reload nginx configuration because nginx need to have the new generated certificates. 
--expand Tell Certbot to expand the certificate with new domaines if the certificate already exists.

HTTPS Nginx configuration

Now we have to configure our HTTP server. In our case we use Nginx.
We first have to generate a dhparam.pem:

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

While you’re at it, you can improve the security of Nginx configuration by hiding Server header. To do so, create the following file:

# /etc/nginx/conf.d/security.conf
server_tokens off;
add_header "Server" "anon.";
proxy_pass_header "Server";

Then we create a snippet that contains the generic ssl configuration:

# /etc/nginx/snippets/ssl.conf
add_header Strict-Transport-Security 'max-age=31536000';

ssl_dhparam /etc/nginx/ssl/dhparam.pem;

ssl_prefer_server_ciphers on;
ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 +SHA !aNULL !eNULL !LOW !MD5 !EXP !DSS !PSK !SRP !kECDH !CAMELLIA !RC4 !SEED';

ssl_protocols TLSv1.2 TLSv1.1 TLSv1;

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

ssl_stapling on;
ssl_stapling_verify on;
resolver_timeout 10;

And to finish we have to update the configuration for our website. Here’s an example with a Rails application:

# /etc/nginx/sites-enabled/myapp.conf
upstream app {
  server unix:///home/deploy/myapp/current/tmp/sockets/puma.sock fail_timeout=0;
}

# http to https redirection
server {
  listen 80;
  server_name myapp.com www.myapp.com;

  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl http2;
	
  server_name myapp.com www.myapp.com;
  root        /home/deploy/myapp/current/public;

  # ssl configuration
  ssl_certificate     /etc/letsencrypt/live/myapp.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
	
  include /etc/nginx/snippets/ssl.conf;
  
  try_files $uri/index.html $uri @app;

  # proxy to unix socket
  location @app {
    proxy_pass http://app;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_redirect off;
  }

  # redirect server error pages to the static page /50x.html
  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   html;
  }

  # assets
  location ~ ^/assets/ {
    gzip_static on;
    expires 1y;
    add_header Cache-Control public;
 
    add_header ETag "";
  }
}

And don’t forget to reload your nginx configuration by running systemctl reload nginx.service. It’s recommended to use reload instead of restart to avoid stopping nginx server. So if you make a mistake in the configuration you are still serving your applications ;)

What about certificates expiration?

One nice thing by installing Certbot with the package manager is that it installs a cron with a renew script (/etc/cron.d/certbot). It renews certificates which are expiring within 30 days.
Because we generate our certificates with --keep-until-expiring and --post-hook "/bin/systemctl reload nginx.service", Certbot will generate the new certificates and reload nginx configuration just after.
So let the magic operate, you have nothing more to do!

Conclusion

And there you have it, a fresh configuration to run your application in https!


Edit 2017-07-29: Added “HTTP Nginx configuration” missing section.