This Blog, Nginx, Let's Encrypt, and

This post is part of a series detailing how this site runs. You can view the previous entry here.

The Domain

The first thing I did after getting the DigitalOcean droplet this site now runs on was to go about getting (which I already owned through Hover) to point to that droplet. This consisted of little more than getting Hover to direct DNS requests for to DigitalOcean's nameservers. There's a tutorial on how to do that here, and although it doesn't directly address Hover, it wasn't all that different from the services described there. From there, I just needed to configure DigitalOcean to direct requests for my domain name to my new droplet.

Nginx & SSL/https

With pointing to my droplet, I next installed Nginx on the machine. Nginx seems to be the most popular web server solution, engineered with the specific goal of outperforming Apache. I don't know if it's now the most popular web server, but I don't know anyone with familiarity with both recommending Apache for new projects.

Anyhow, installing Nginx on Ubuntu consisted of two commands:

sudo apt-get update
sudo apt-get install nginx

Nginx serves two purposes. The first is that it's capable of handling a lot of web traffic efficiently, and tunneling that traffic to other web apps through what's called a reverse proxy. Right now it tunnels all traffic to my Ghost instance (which I'll detail in another post), but in the future I could theoretically have a subdomain (e.g. point to the same droplet, and Nginx could tunnel all traffic to that subdomain to an entirely different web app running on the same machine.

The second and more important purpose Nginx serves is related to the first: it has a robust and capable implementation of SSL, which is the protocol used for securely encrypting web traffic. Suffice to say that if I want to have my site accessible over https, I need a web server that implements SSL. The problem is, cryptography is hard, and most web app developers have neither the time nor aptitude to implement these encryption protocols perfectly. So, you let Nginx do the hard work of establishing https connections with the users of your website, and then tunnel those connections to your web app (in my case, Ghost) over http. This is safe and secure because, although the connection between Nginx and the web app is unencrypted, it's between two processes on the same machine, so it's never exposed to the public internet.

Setting up https

Anyhow, all that then remains is setting up Nginx to serve https connections, and thankfully that has become immensely easier in recent years thanks to Let's Encrypt, which provides free SSL certificates. Their tool, Certbot, is in my view the easiest way to use their service. You can follow along their guide, but just make sure you already have an Nginx configuration (i.e. a server block) for the domains you're registering. I forgot to edit that first, and Certbot got confused when it didn't find any Nginx configuration having to do with

cd /etc/nginx/sites-available
sudo cp default

Where the contents of look something like:

server {
    listen 80 default_server;
    listen [::]:80 default_server;

The catch to this mostly painless setup is that your certificate through Let's Encrypt needs to be renewed every 90 days. Renewing the certificate is (predictably) rather trivial with Certbot, and there's no issue if you try to renew it too early, so most folks set up their machines to renew their certificates through Certbot every week or even every day.

I ended up putting mine in a script rather than directly embedding the command in a cronjob. I named the script lets_encrypt_renew_certs, made sure it was executable with sudo chmod +x lets_encrypt_renew_certs, and put it in the directory /etc/cron.daily/. This isn't as fine grained as specifying a time to run in the crontab, but Cron will make sure the script is run sometime each day all the same, which is more than enough for my needs.

#!/usr/bin/env bash
# This simple script renews the SSL certificates for the current machine
# using Let's Encrypt's certbot program. At the time of this writing,
# the domains this renews the certificates for are and
#, although it should work no matter which domains are
# currently being managed by certbot.
# The -q option silences all output except for errors.
# After the certificate is renewed Nginx will need to refresh its
# configuration to make use of the new(?) certificates, so the
# --renew-hook option makes systemd direct nginx to reload its config

certbot renew -q --renew-hook "systemctl reload nginx.service"

Configuring Nginx

Now that Nginx is configured to handle SSL appropriately, I need to confgure Nginx itself. When I was setting all this up, I actually just made sure connections to were working, and that http connections were getting redirected to https (which Certbot should offer to configure for you), and then left Nginx serving the default "Welcome to Nginx" landing page for a while until I set up my Ghost instance. I'll detail how I installed/run Ghost in my next post, but for right now just presume that I have it running on localhost:2368—in other words, I'm running it on the droplet on port 2368 (the default Ghost port), but it's only accessible to other processes on the machine itself. With that all said, my Nginx configuration looks like the following:

# This file is intended to be placed in /etc/nginx/sites-available or some equivalent location,
# presumably with a name like `` or something like that.
server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /var/www/html;


    # This effectively sets the max file upload size for the site,
    # such as uploading images for profiles or articles.
    client_max_body_size 5m;

    location / {
        # These three next directives set http headers which help the
        # Ghost web app understand the details of the original request
        # that nginx recieved
        # Who sent the original request?
        # What's the ip address of the original sender of this request?
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        # What was the host/domain the original sender of this request
        # was trying to connect to?
        proxy_set_header Host $http_host;
        # What was the protocol the original sender of this request used
        # in trying to connect to this site?
        proxy_set_header X-Forwarded-Proto $scheme;
        # Tunnel this request to the web app running on localhost:2368

    # Everything below here is SSL/https configuration stuff added by Certbot.

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot

    # Force users to connect over https by redirecting all http requests to the
    # equivalent https URL.
    if ($scheme != "https") {
        return 301 https://$host$request_uri;
    } # managed by Certbot

Show Comments