Small Data

Blog

There is no functionality for comments at the moment but please feel free to get in touch via the contact section :)

RSS feed

Nginx, Node.js and HTTPS via Let's encrypt

This is a follow-on post from a previous entry where we setup multiple Node.js websites on a single machine with the help of Nginx. In this article we will add https to one of our domains thanks using free SSL certificates provided by Let's encrypt.

It is required to run the setup on the machine that we will be hosting the domain for which we will be requesting a certificate. Let's encrypt comes with tools that work best on a linux system and even have capabilities to auto-configure popular web servers like Apache and Nginx. We will ony be using it as a standalone application to get the SSL certificates :)

Installing Let's encrypt

Simply clone the github repository.

git clone https://github.com/letsencrypt/letsencrypt
Create your SSL certificate

Run the following command and make sure to replace smalldata.tech with your domain name. Note that in our case, we have two domains that point to the same setup and therefore we have requested a combined certificate for both of them.

    A few important notes:
  • If you already have a server running on port 80, then letsencrypt will complain and you will need to shutdown the process to allow the generation of the SSL certificate.
  • Port 443 needs to be open and accessible on the machine

cd letsencrypt
./letsencrypt-auto certonly --standalone --email admin@smalldata.tech -d smalldata.tech -d www.smalldata.tech -d smalldata.website -d www.smalldata.website

Your certificate should now be available at /etc/letsencrypt/live/smalldata.tech.

Update March 29, 2016
In case you get a weird pip cryptography error then it is likely a memory issue and can be fixed by adding a swap file to your system as follows:
sudo dd if=/dev/zero of=/swapfile bs=1024 count=524288
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
Update June 27, 2016
The above cryptography error looks like the following:
Command "/home/xxx/.local/share/letsencrypt/bin/python2.7 -u -c "import setuptools, tokenize;__file__='/tmp/pip-build-R5zGBV/cryptography/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-cf8qxj-record/install-record.txt --single-version-externally-managed --compile --install-headers /home/xxx/.local/share/letsencrypt/include/site/python2.7/cryptography" failed with error code 1 in /tmp/pip-build-R5zGBV/cryptography/
You can also add the following line to /etc/fstab to have your swap enabled even after a reboot:
/swapfile1 none swap sw 0 0
Setup Nginx to use the created certificate

If you followed the steps from the previous tutorial then you probably have a nginx server configuration for your domain : /etc/nginx/sites-available/smalldata.tech. You will need to modify it as follows to use the ssl certificate:

server {

    listen 80 default_server;
    server_name smalldata.tech www.smalldata.tech smalldata.website www.smalldata.website;

    # redirect all urls to https
    return 301 https://$server_name$request_uri;
}

server {

    listen 443 ssl;

    server_name smalldata.tech www.smalldata.tech smalldata.website www.smalldata.website;

    # add Strict-Transport-Security to prevent man in the middle attacks
    add_header Strict-Transport-Security "max-age=31536000";

    # ssl certificate config
    ssl_certificate /etc/letsencrypt/live/smalldata.tech/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/smalldata.tech/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/smalldata.tech/chain.pem;

    access_log /var/log/nginx/smalldata.tech.log;

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;

        proxy_pass http://127.0.0.1:your-port;
        proxy_redirect off;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

    A few notes on the above configuration:
  • Don't forget to replace your-port with the actual port number. Also, needless to say you will need to change the domain names as per your requirements
  • Automatically forward all requests to https
  • Follow SSL security guidelines as mentioned here. Most importantly, disable SSL3 which has a known vulnerability

Make sure to restart nginx for your changes to take effect.

sudo service nginx restart
Test your new configuration

SSL labs provides a free SSL audit which you can use to see that everything is working properly.

Automatic certificate renewals

At the time of writing, the certificates provided by let's encrypt are valid for short periods of time (~3 months) and do not renew automatically. This needs to be manually done but we can simply setup a cron job to do that for us.

To get around the previous problem of having to stop Nginx to allow for domain name verification, we can use the webroot plugin which basically sets up a secret file under the root folder of the website that is being served via nginx to verify the domain name. The following is an example of a script that checks if the certificate is about to expire and renews it if yes:


#!/bin/bash

web_service='nginx'
le_path='/home/ubuntu/letsencrypt'
exp_limit=30;
domain='smalldata.tech'
cert_file="/etc/letsencrypt/live/$domain/fullchain.pem"

exp=$(date -d "`openssl x509 -in $cert_file -text -noout|grep "Not After"|cut -c 25-`" +%s)
datenow=$(date -d "now" +%s)
days_exp=$(echo \( $exp - $datenow \) / 86400 |bc)

echo "Checking expiration date for $domain..."

if [ "$days_exp" -gt "$exp_limit" ] ; then
    echo "The certificate is up to date, no need for renewal ($days_exp days left)."
    exit 0;
else
    echo "The certificate for $domain is about to expire soon. Starting webroot renewal script..."
        $le_path/letsencrypt-auto certonly -a webroot --agree-tos --renew-by-default --webroot-path=/home/ubuntu/server/public/ -d smalldata.tech -d www.smalldata.tech -d smalldata.website -d www.smalldata.website
    echo "Reloading $web_service"
    /usr/sbin/service $web_service reload
    echo "Renewal process finished for domain $domain"
    exit 0;
fi

sudo crontab -e

30 2 * * 1 /home/ubuntu/le-renew.sh >> /var/log/le-renewal.log

    A few notes about the above cron job:
  • Run every Monday at 2:30 am
  • Outputs the value of the script at /var/log/le-renewal.log

References
  • Tutorial by Mitchell Anicas for setting up the renewal cron job.
  • Many thanks to the folks at Let's encrypt for making true free SSL certificates a reality!