Victor is a full stack software engineer who loves travelling and building things. Most recently created Ewolo, a cross-platform workout logger.
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 :)

Update June 5, 2017
This article has been updated to provide instructions using certbot.
Installing certbot

On a ubuntu system run the following:

sudo apt-get install software-properties-common
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install certbot
Create your SSL certificate

Run the following command and make sure to replace 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 certbot 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

sudo certbot certonly --standalone --agree-tos -m -d -d -d -d

Your certificate should now be available at /etc/letsencrypt/live/

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/';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/ You will need to modify it as follows to use the ssl certificate:

server {

    listen 80 default_server;

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

server {

    listen 443 ssl;


    # 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/;
    ssl_certificate_key /etc/letsencrypt/live/;
    ssl_trusted_certificate /etc/letsencrypt/live/;

    access_log /var/log/nginx/;

    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_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

Update June 5, 2017
To further increase security, you should also generate a strong Diffie-Hellman group. To generate a 2048-bit group, use this command:
sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
Nginx can then be configured with the following (note the SSL security guidelines as mentioned above):
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;
    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;

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

To be able to renew the certificates automatically we need to be able to let the authentication take place while Nginx is running. 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:

sudo certbot certonly --webroot --agree-tos --webroot-path=/home/ubuntu/server/public/ -d -d -d -d

Add the --force-renewal option to force certificate renewal.

Once certificates have been renewed, the settings are saved in the renewal folder. For e.g. /etc/letsencrypt/renewal/

# renew_before_expiry = 30 days
cert = /etc/letsencrypt/live/
privkey = /etc/letsencrypt/live/
chain = /etc/letsencrypt/live/
fullchain = /etc/letsencrypt/live/
version = 0.14.2
archive_dir = /etc/letsencrypt/archive/

# Options and defaults used in the renewal process
installer = None
authenticator = webroot
account = xxx
[[webroot_map]] = /home/ubuntu/server/public = /home/ubuntu/server/public

This allows simply running the following to renew certificates:

sudo certbot renew

Add the --dry-run option to see that everything is working before actually going ahead with the renewal.

We can now setup a simple cron job to perform the renwal:

sudo crontab -e

30 2 * * 1 certbot renew --renew-hook "/usr/sbin/service nginx reload"

    A few notes about the above cron job:
  • Run every Monday at 2:30 am
  • Only reload Nginx if there were certificates to be renewed
  • Creates a log file under /var/log/letsencrypt/letsencrypt.log

  • 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!