Setting Up SSL for Ghost on DigitalOcean with Lets Encrypt

After reading entirely too many articles and a lot of trial & error, I've finally managed to get HTTPS working on my DigitalOcean-powered, Nginx-backed Ghost blog using LetsEncrypt's free SSL certificates. For years people have argued whether it's worth setting up SSL on websites that have no inherent need for strong security (credit card processing, user logins, etc), but now that SSL certificates have next to no cost and Google is

Having [regretfully] setup SSL certificates on Apache servers numerous times in the past, it's not at all surprising that this process was a headache to sort out, though it seems like LetsEncrypt is on the right path to simplifying the process with its command line toolkit (which regrettably doesn't have an automated install for Nginx quite yet). Fortunately, it does provide one-line commands to get a folder of bare certificates for both single or multiple domains that works on any server.

The Guide

All steps are written from the viewpoint of a Ghost blog that was setup on DigitalOcean using it's one-click installer, but they're likely applicable to most Nginx/Ghost setups in general. Regardless you should probably be familiar with using a terminal and SSH — if you aren't I highly recommend brushing up via those links before you get started.

  1. If you already have a Ghost blog setup then skip to step 2. If not, go ahead and create a new droplet on DigitalOcean using the Ghost “One-Click App” install (Ghost 0.7.9 on Ubuntu 14.04 as of this writing) with 512mb of memory1 (the $5/month option). Feel free to choose whichever datacenter is closest to yourself (or your users) and add an SSH key so you can access your Droplet via your local terminal.
  2. Once the droplet is finished building, access your droplet over SSH via the terminal2 of your choosing with ssh root@0.0.0.0, where 0.0.0.0 is the IP address of your droplet as listed in your account. If it prompts you to trust the RSA key type yes and press ENTER. Long-term you should probably disable the root user, but that's outside the scope of this guide.
  3. Once you're inside your server (synonymous with “droplet”), run apt-get update && apt-get upgrade -y to bring all Linux dependencies up to date.
  4. Next go ahead and open your Nginx configuration with vim /etc/nginx/sites-available/ghost and update it to match the block below — available as a gist here — replacing the six (6) yourdomain.com instances accordingly (if you have an older droplet this file may not exist, in which case you can either edit /etc/nginx/conf.d directly or set this up yourself).2
  5. Run service nginx restart to restart Nginx and adopt the new settings. If you get a [fail] response run nginx -t to automatically detect where the error occurred.
  6. Run cd /opt && wget https://dl.eff.org/certbot-auto && chmod a+x certbot-auto. This will copy the repository from LetsEncrypt's mirror on the EFF website and set the correct permissions for it to run safely.3
  7. Run /opt/certbot-auto certonly --webroot -w /var/www/ghost -d yourdomain.com -d www.yourdomain.com, replacing “yourdomain.com” in both entries. Fill out your email address and agree to the TOS if prompted, and otherwise sit back and sip a cup of coffee while it does its thing. This will create a hidden .well-known folder within your /var/www directory and check against it from the LetsEncrypt servers. Note: Your website/server must be publicly accessible over port 80 for LetsEncrypt to verify your domain.
  8. Remove or comment out the first 12 lines of the Nginx config, and then uncomment (remove # signs) from the second half of the settings in /etc/nginx/sites-available/ghost, then run service nginx restart again to adopt the new settings.4
  9. Test https://yourdomain.com to make sure that SSL is working properly.
  10. Lastly, you'll want to set up a cronjob to automatically renew the certificate every 60 days or so, otherwise they'll expire after 90 days. Go ahead and run crontab -e in your server terminal, and you should see something similar to the image below. On the last line add 0 0 1 */2 * /opt/certbot-auto renew --quiet --no-self-upgrade, which tells cron to rerun the certificate renewal for you on the first day of every other month.

Crontab Screenshot

And that's it! If you have any issues or find an error feel free to comment on the gist, tweet at me, or message me via the contact page.

Nginx Config

Redirecting From non-www to www Instead

I've personally always favored non-www addresses, but here's instructions to redirect to www. prefixed URLs instead. In short all you need to do is reverse the www/non-www redirects by following the instructions below. Here's an alternative gist that shows how it should end up after removing the first temporary block from lines 1-12.

  1. Add www. into the URL on line 17.
  2. Remove www. from the URL on line 22 and add www. to line 23.
  3. Add www. into the URL on line 37.

Updates

  • August 11, 2016: Updated guide to add steps breaking apart enabling HTTP, verifying LetsEncrypt, and enabling HTTPS.
  • October 19, 2016: Updated the Nginx config to have the default root directory as /var/www/ghost instead of just /var/www.
  • January 24, 2017: Updated the Nginx config to fix a bug where www to non-www redirects wouldn't occur properly on HTTPS URLs, and added instructions on how to redirect from non-www to www instead.
  • July 3, 2017: Removed lines 59-61 in the Nginx config Gist and code snippet, which previously manually routed the robots.txt and sitemap.xml, as Ghost now handles these automatically for the past several major versions. Huge thanks to both Tom Blackshire & Grant Whinney for both giving me the heads up!
  1. If you're worried about only having 512mb of memory feel free to upgrade, though this blog has handled upwards of 650 concurrent visitors without skipping a beat.

  2. If you're interested in learning more about Nginx configuration you can read up on it on their official wiki, https://www.nginx.com/resources/wiki/start/.

  3. You can find more documentation on letsencrypt options at https://letsencrypt.org/getting-started/.

  4. The new first block will 301 redirect all non-HTTPS requests, the second block will 301 redirect all HTTPS requests if a user manually enters a www URL, and the third is the actual block routing all requests to Ghost after redirects are finished.