How to Set Up HTTPS for Free on a Dockerized Ruby on Rails Production Server

I recently launched a Ruby on Rails based application and wanted to add HTTPS to my domain. In this post, I'm going to show you how to add HTTPS to your domain using a tool called Certbot.

Why do you need HTTPS?

HTTPS is an essential service for modern web applications. An HTTPS-enabled website will encrypt all of the data sent between you and the website server. This protects your passwords and other sensitive information from third parties on the Internet such as other users on your local Wifi network. HTTPS can also be used as a means to help users identify that they are on a correct website and not a scam website doctored to look like an authentic website. With the major web browsers like Chrome now labeling non-HTTPS enabled websites as insecure, HTTPS may soon be a required feature for website delivery.

Screen shot 2019 02 23 at 10.45.25 pm
Example of how an HTTPS enabled site displays
Screen shot 2019 02 23 at 10.48.14 pm
Example of how a not-HTTPS enabled site is displayed (as insecure)

Prerequisites

This particular HTTPS setup is making several assumptions on your general website setup. In this case I had the following pieces of software already running and configured:

  1. Ubuntu Linux 18.04 (Bionic Beaver) - this won't work for Heroku or Windows.
  2. Application running on Docker
    • Rails app already installed on the server via Capistrano and running via Docker
  3. NGINX installed on the server (but you can also run it via a Docker container)
  4. Certbot installed

What is NGINX?

NGINX is a Node.js based web server. In the Javascript world it can be used similarly to WEBRICK, Puma, or Thin of the Ruby world. You could whole Javacsript applications served by NGINX. It's also known for it's speed, which has similar performance benchmarks to Apache HTTP Server.

In this example NGINX serves three functions: 1) Integrate our HTTPS certificate into transport layer. 2) Redirect traffic seeking the HTTP port 80 to the HTTPS port 443 to force clients into using the more secure protocol. 3) Route web traffic to the appropriate container for the domain name of the request. This will allow us to serve multiple websites using the same Linux server.

This kind of setup for routing web traffic is called a reverse-proxy.

Screen shot 2019 02 24 at 2.12.21 pm
How the NGINX reverse proxy routes traffic to the appropriate application port based on the domain

How do you install NGINX?

Installation instructions are found on the official NGINX website. For this example instance, I installed NGINX using the apt-get repository:

$ sudo apt-get install nginx

Once it's installed, a useful command to run is the configuration test, which verifies the OK status of its configuration settings. $ sudo nginx -T nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful

What is Let's Encrypt and Certbot?

Let's Encrypt is a nonprofit organization that promises to be the Internet's free, automated, and open Certficate Authority (CA). Several companies such as Symantec, DigiCert, GoDaddy are also CA's and offer certificates for a fee. For HTTPS to work, your server needs to install a certificate from one of these companies, which is nothing more than a private encryption key associated with your domain. When a client connects to your website, your server will send a message encrypted with that private key, which can be decoded by a public key on the client's end. HTTPS enabled web browsers like Firefox or Chrome come with these public keys preinstalled, per an arrangement with the CA companies. If you want a more general overview of how HTTPS certificates work, here is a great YouTube video explaining the process.

Let's Encrypt has made the installation and renewal of SSL Certificates easy with a tool called Certbot. Certbot validates your server to the Let's Encrypt by having it perform what it calls an ACME challenge. An example would be when you run Certbot, it will make a temporary file available via HTTP which the Let's Encrypt organization can access. Let's Encrypt will then check the DNS records to verify that the domain name you want to certify points to the server you are running Certbot from. If the DNS matches, then Let's Encrypt will send a newly issued certificate to your Certbot.

Certbot Tnstallation

Installation instructions are available on the Certbot user guides, but here is a summary of the commands I used to install Certbot:

$ sudo apt-get update
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository universe
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install certbot python-certbot-nginx

Notice that I also installed the python-certbot-nginx module, which invokes our instance of NGINX in performing the challenge.

Configure the DNS to Point to the Server's IP Address

All you need is an A or CNAME record pointing your domain to the IP address of your Linux server.

Run the Certbot ACME Challenge to Verify the Domain

sudo certbot --nginx -d my-example-domain.com -d www.my-example-domain.com

Passing the nginx flag tells Certbot to use NGINX to perform the ACME challenge. Certbot temporarily modifies the NGINX settings so that the remote Let's Encrypt system can find a file on your system to complete the challenge. Once complete, it will automatically install the received SSL certificate, which will act as a private key during the HTTPS initiation.

Update the NGINX settings to forward HTTP to HTTPS

Certbot should update your NGINX settings automatically, but here's now it will look after.

# /etc/nginx/sites-available/my-example-domain.com

server {
    root /var/www/my-example-domain.com/current/public; # Directly serves anything in the Rails public folder
    index index.html index.htm index.nginx-debian.html;
    server_name my-example-domain.com www.my-example-domain.com; # managed by Certbot

    location / {
        proxy_pass http://localhost:8030; # 8030 is the port the Docker container is running on
          proxy_set_header Host $host;
          #try_files $uri $uri/ =404;
    }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/my-example-domain.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/my-example-domain.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
    if ($host = www.my-example-domain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = my-example-domain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80 ;
    listen [::]:80 ;
    server_name my-example-domain.com www.my-example-domain.com;
    return 404; # managed by Certbot
}

NGINX has two important folders for domain settings: /etc/nginx/sites-available /etc/nginx/sites-enabled

The sites-available folder has the NGINX proxy settings for each individual website. The sites-enabled should contain symlinks to the files in the sites-available folder. Create a symlink at the command line like this:

$ ln -s /etc/nginx/sites-enabled/my-example-domain.com /etc/nginx/sites-available/my-example-domain.com

Additional Resources:

  1. https://blog.heckel.xyz/2013/07/01/how-to-use-mitmproxy-to-read-and-modify-https-traffic-of-your-phone/
  2. https://www.youtube.com/watch?v=iQsKdtjwtYI