< go back to home

How To Setup SSL Using Let's Encrypt To An Nginx Reverse Proxy NodeJS Site

July 15, 2017

Serving websites through HTTPS is essential and will soon be a requirement based on the direction of major browsers' policies. Traditionally, the TLS/SSL certifications recognized by browsers can be obtained through CA (Certificate Authorities) companies like GoDaddy and Namecheap which you have to pay at around 10$ a year. Let's Encrypt aims to improve this by providing an open, free and automated way of obtaining certificates.

For this article, I'll show you how to use Let's Encrypt to enable a NodeJS site to be serverd in HTTPS. This is how this website of meine was setup and this also serves as my documentation.

Requirements

Install Certbot

Certbot is a CLI tool for obtaining certificates. Let's install Certbot:

sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install certbot

Setup Nginx For Web-Root

Let's Encrypt have different methods for acquiring certificates. As we are using Nginx, we will be using the Webroot mode which works by providing a ".well-known" directory that can be accessed by Let's Encrypt through "/.well-known" URL like "http://mysite.com/.well-known". Being able to access this will prove to Let's Encrypt that we have the authority to the domain.

This is the part where it gets tricky for reverse-proxy architecture and ExpressJS as the routing is not file or path based. Fortunately, Nginx can configure separate rule for specific path. Let's do that, open your Nginx config file in "/etc/nginx/sites-available" and add the following lines of code in your port 80 server block:

location /.well-known {
    root /var/www/ssl-proof/;
}

We are telling Nginx to serve the contents of "/var/www/ssl-proof/.well-known/" if the URL is "http://yoursite.com/.well-known" instead of going through ExpressJS routing. Your configuration should look like this:


server {
    listen 80;
    listen [::]:80 default_server ipv6only=on;
    server_name mysite.com;

    # Go to /var/www/ssl-proof/.well-known
    location /.well-known {
        root /var/www/ssl-proof/;
    }

    # Pass requests for / to localhost:3000:
    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-NginX-Proxy true;
        proxy_pass http://localhost:3000/;
        proxy_ssl_session_reuse off;
        proxy_set_header Host $http_host;
        proxy_cache_bypass $http_upgrade;
        proxy_redirect off;
    }
}

Let us restart Nginx for the configuration to take effect. Note though that we still have to setup the Web-Root directory for ".well-known" which is tackled in the next section.

sudo service nginx restart

Setup Web-Root Directory

Let's now create the web-root directory:

mkdir -p /var/www/ssl-proof/.well-known
echo "nginx is awesome!" > /var/www/ssl-proof/.well-known/test.html

The file "test.html" won't be used by Let's Encrypt. We'll just use that to verify that the path is working. Go ahead and check it in your browser e.g. "http://yoursite.com/.well-known/test.html". If everything work well as shown below, we can now acquire our SSL certificate.

Verification that well-known URL path is working.

Accquire SSL Certificate

sudo certbot certonly --webroot --webroot-path=/var/www/ssl-proof -d mysite.com

The command above will generate the necessary keys located in "/etc/letsencrypt" folder. You don't have to touch those generated files. We just have to update now our Nginx configuration file to use those keys and enable HTTPS. So from the file earlier, edit it to be similar to the one below. Refer to the comments for the explanations.

# HTTP - redirect all requests to HTTPS:
server {
    listen 80;
    listen [::]:80 default_server ipv6only=on;
    server_name cmpalce.com;
    return 301 https://$host$request_uri;
}

# HTTPS - proxy requests on to local Node.js app:

server {
    listen 443;
    server_name mysite.com;

    ssl on;
    # Use certificate and key provided by Let's Encrypt:
    ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mysite.com.com/privkey.pem;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;

    location /.well-known {
        root /var/www/ssl-proof/;
    }

    # Pass requests for / to localhost:3000:
    location / {

        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-NginX-Proxy true;
        proxy_pass http://localhost:3000/;
        proxy_ssl_session_reuse off;
        proxy_set_header Host $http_host;
        proxy_cache_bypass $http_upgrade;
        proxy_redirect off;
    }
}

Restart Nginx and check your site in HTTPS now.

sudo service nginx restart

Manual Renewal of Certificate

Run the following command to renew certificate with regards to expiration date:

certbot renew
systemctl restart nginx

References




< go back to home