Let's Encrypt, Serverpilot + Cloudflare

Ever since I saw Let’s Encrypt pop up on HN I’ve been pretty excited about it’s release. Unfortunately, I’ve been a bit slow setting it up as a week or two ago they hit 1,000,000 certificates issued…

Finally, I’m ready though. There are a few guides out there about how to set it up but I haven’t found anything conclusive for setting it up with Server Pilot and Cloudflare. Probably the best of these for using Let’s Encrypt is the Digital Ocean one which I’ve based the below on, the main differences are that I’ve used a stronger DH Param, have a slightly different cipher list and have a few Serverpilot specific bits. I found something else quite interesting about securing Nginx take a look here if you’re interested. Due to these great articles I’ve kept the below quite brief.

First off we need to SSH into our server, I’m going to log in as root for simplicity.

ssh root@123.123.123.123

Clone Let’s Encrypt into the users home directory:

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

Now generate the certificate. I found it more reliable to use the webroot method for verification due to being behind Cloudflare. You’ll be asked for an email address and to accept the terms and conditions, also, remember to update APP_NAME and DOMAIN_NAME.

sudo ~/letsencrypt/letsencrypt-auto certonly -a webroot --webroot-path=/srv/users/serverpilot/apps/APP_NAME/public/ -d DOMAIN_NAME -d www.DOMAIN_NAME

It is recommended to create a strong Diffie-Hellman group (at least 2048 bit, but we’ll do 4096):

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

Now we have our certificates sorted we need to have Nginx know about this and force traffic over HTTPS. The config we need to modify is located at /etc/nginx-sp/vhosts.d/APP_NAME.conf, however, if we modify this Serverpilot will overwrite it. To prevent this they advise us to copy the original config to a APP_NAME.custom.conf, this also gives us a handy backup:

cp /etc/nginx-sp/vhosts.d/APP_NAME.conf /etc/nginx-sp/vhosts.d/APP_NAME.custom.conf

Now open APP_NAME.conf in your favourite editor (vim for me):

vim /etc/nginx-sp/vhosts.d/APP_NAME.conf

The first thing we’ll do is remove the lines which are listening for traffic on port 80, http:

listen       80;
listen       [::]:80;

In their place we’ll listen for traffic on port 443, https:

listen 443 ssl;

Then under the server name lines add the following to let Nginx know where the certificate is:

ssl_certificate /etc/letsencrypt/live/DOMAIN_NAME/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/DOMAIN_NAME/privkey.pem;  

Next we need to set up SSL to use our generated DH param along with a few extra bits. I’ve put this just above the includes at the bottom:

#Support only TLS, not SSLv2 or SSLv3
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

ssl_prefer_server_ciphers on;
ssl_dhparam /etc/ssl/certs/dhparam.pem;

ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA;

ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security max-age=15768000;

Finally, we’ll redirect http traffic to https. Outside of the first server{} block add this extra one (again, remember to alter DOMAIN_NAME):

server {
    listen 80;
    server_name DOMAIN_NAME www.DOMAIN_NAME;
    return 301 https://$host$request_uri;
}

For completeness below is a copy of my config with example.com as the domain name and example_app as my app name:

server {
    listen 443 ssl;

    server_name example.com www.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    root   /srv/users/serverpilot/apps/example_app/public;

    access_log  /srv/users/serverpilot/log/example_app/example_app_nginx.access.log  main;
    error_log  /srv/users/serverpilot/log/example_app/example_app_nginx.error.log;

    proxy_set_header    Host              $host;
    proxy_set_header    X-Real-IP         $remote_addr;
    proxy_set_header    X-Forwarded-For   $proxy_add_x_forwarded_for;

    #Support only TLS, not SSLv2 or SSLv3
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA;

    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_stapling on;
    ssl_stapling_verify on;
    add_header Strict-Transport-Security max-age=15768000;

    include /etc/nginx-sp/vhosts.d/example_app.d/*.nonssl_conf;
    include /etc/nginx-sp/vhosts.d/example_app.d/*.conf;
}

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

To apply these changes we just need to reload the Nginx config:

service nginx-sp reload

Now you should have a working SSL configuration. Try accessing your site over both https and http to check it’s all working correctly.

Let’s Encrypt issues certificates for 90 days and therefore they need to be renewed. Luckily, we can do this using their CLI tools:

~/letsencrypt/letsencrypt-auto renew 

We can put this in a Cron job to have it done automatically, run sudo crontab -e and add the following lines (remembering to update the path to letsencrypt if you’re not logged in as root):

00 16 * * 1 /root/letsencrypt/letsencrypt-auto renew >> /var/log/le-renew.log && echo "" << /var/log/le-renew.log
05 16 * * 1 service nginx-sp reload

This will trigger a renew every Monday at 4PM, then 5 minutes later will reload Nginx config. You can view /var/log/le-renew.log to check the status of renews.

It’s worth pointing out that when issuing the second certificate on the server you don’t need to generate another DH param or modify Cron.

That’s all, to test your configuration you can go to here or here.

comments powered by Disqus