Setting Up SSL for Ghost on DigitalOcean with Lets Encrypt

After reading entirely too many articles and a lot of trial & error, I've written this guide on how to get HTTPS working on a 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 are free for anyone and Google is prioritizing HTTPS sites over non-HTTP sites there's no excuse not to.

While DigitalOcean's pre-made Ghost Droplet gets us pretty far, there's still a few steps to get HTTPS and your custom domain running.

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 1.2.11 on Ubuntu 16.04 as of this writing — we'll update it to the latest Ghost version in a few steps!) with 512mb of memory[1] (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 terminal 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.
  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. If it prompts you to setup mail config or provides a warning about the /root/grub/menu.lst I recommend choosing the default "No Configuration" and "Keep local..." unless you have reason to do otherwise.
  4. Switch to the "ghost-mgr" user DigitalOcean already setup by running su - ghost-mgr (su = switch user).
  5. Install Node Version Manager (NVM) so we can easily update to a more recent version of Node by running curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash. Activate the nvm bash alias by running source ~/.bashrc or exit'ing and switching back to the ghost-mgr user.
  6. Install Node v8.9.3 and set it as the default by running nvm install 8.9.3 && nvm alias default node. You can check that the setup worked by running node -v, which should return v8.9.3.
  7. Install the Ghost CLI with sudo npm i -g ghost-cli so we can easily upgrade to the latest version of Ghost (v1.23.0 as of current) now and upgrade easily in the future.
  8. Run cd /var/www/ghost/ to switch into your project's folder, and then ghost update to update to the latest version.
  9. Next let's install LetsEncrypt's CertBot to get our SSL certificate — type exit to go back to the root user (or start a new SSH session as root) and install the CertBot package with...
    sudo apt-get install software-properties-common
    sudo add-apt-repository ppa:certbot/certbot
    sudo apt-get update
    sudo apt-get install python-certbot-nginx
  10. Run sudo certbot --nginx and follow the prompts along. Make sure to add a real email as they'll send you SSL renewal reminders, and select 2: Redirect when prompted so all visitors are forced to the HTTPS version of your site.
  11. Run vim /var/www/ghost/config.production.json to edit the url attribute to reflect your site's domain name (in my case url: "https://robertnealan.com",, exit, and then run ghost restart (make sure you're in your project directory!).
  12. Remove the default IP-based Nginx config by running cd /etc/nginx/sites-available && ls, and then removing the IP address file with rm 0.0.0.0. Next open the default config in vim and edit it to match the configuration file below, replacing all instances of robertnealan.com with your own respective domain name[2].
  13. Run service nginx restart — if you get any errors run nginx -t and it should point you to any syntax errors you might have made in the /etc/nginx/sites-available/default conf file.
  14. Test https://yourdomain.com to make sure that SSL is working properly...if it is then celebrate! 🎉
  15. 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. First run /usr/bin/certbot renew --dry-run to make sure it'll work as expected. Next 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 * /usr/bin/certbot renew --nginx --quiet, which tells cron to rerun the certificate renewal for you on the first day of every other month. With this cron in place you can safely ignore the renewal emails from LetsEncrypt.

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

Other Basic Ghost Setup Tasks

Redirecting From non-www to www Instead

I've personally always favored non-www addresses, if you prefer to have the www version then make sure to use www.yourdomain.com during the CertBot registration, and subsequently make sure the server_name directives in the Nginx default conf file have www.yourname.com rather than yourdomain.com.

Locking Down Your Server

Coming soon!

Setting up Email Sending

Coming soon!

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!
  • May 23, 2018: Fully overhauled the post to use DigitalOcean's newer Ghost Droplet, Ubuntu 16.04, the new Ghost CLI, and the latest version of LetsEncrypt's CertBot.

  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/. ↩︎