Deploying Ghost with Caddy


Note: These instructions are long out-of-date. Ghost can no longer be setup this way.

In my experience, setting up a new blog is a pain: you buy a domain, buy an SSL cert, setup a droplet from Digital Ocean, apt-get a bunch of stuff, attempt to scp over some stuff from your machine, write an upstart job, accidentally upgrade to Ubuntu 16, write a systemd job, fight with those jobs, fight with nginx for an hour, and eventually you'll have a half working blog. Not anymore though! Here's the relatively efficient way to setup a new blog with Ghost and Caddy on Digital Ocean. We're going to cut some corners here to make the setup as easy as possible.

Step 1: Buy a domain

Go buy a domain from somewhere – it doesn't matter where. Once that's done, point the nameservers to,, and

Step 2: Get a droplet from Digital Ocean

Go to Digital Ocean (that's my affiliate link btw) and click the green "Create Droplet" button in the top right. After that, click the "One-Click Apps" tab, and select "Ghost on 14.04". Get whatever droplet size you feel like, pick your region, and add your SSH keys if you want/know how (we're not going to do that though). Change the name to something that makes sense, and create your droplet.

One your droplet is created, go to the networking tab, and add the domain you just bought by typing in your domain name in the "Domain" box, and then selecting your new droplet in the "Droplet or custom IP" box. Hit "Create Record", and that's it.

Step 3: SSH into your droplet

Check your email for your throwaway password, then SSH into your droplet by running ssh root@<your-ip>. Enter your throwaway password, and follow the steps to change it.

Step 4: Let Ubuntu update itself

RIP to upstart, and praise systemd.

Step 5: Install and setup Caddy

Run service nginx stop, since we'll be using Caddy instead. Install Caddy by running curl | bash, which will detect which OS you've got, and install Caddy based on that.

Caddy will need to bind to certain ports, so run setcap cap_net_bind_service=+ep $(which caddy).

We'll also need a Caddyfile and a place to put it. Create a directory for it by running mkdir /var/www/caddy, then create the Caddyfile at /var/www/caddy/Caddyfile. Your Caddyfile should contain this:

    proxy / localhost:2368 {
      proxy_header Host {host}
      proxy_header X-Real-IP {remote}
      proxy_header X-Forwarded-Proto {scheme}
    tls <YOUR-EMAIL>

Where <YOUR-EMAIL> needs to be the domain you bought in the format, and <YOUR-EMAIL> just needs to be your email.

You'll need to create two systemd service files: one for Ghost, and one for Caddy. Let's start with the one for Caddy. First, create a directory to put your SSL certs in by running sudo mkdir -p /etc/ssl/caddy && sudo chown -R ghost:ghost /etc/ssl/caddy. Note that because we used the One-Click image to setup our droplet, the user ghost already exists on our machine. Next, create the file /lib/systemd/system/caddy.service, and put the following in it:

  ExecStart=/usr/local/bin/caddy --conf /var/www/caddy/Caddyfile --agree --email '<YOUR-EMAIL>'  

Replace <YOUR-EMAIL> with your actual email (the same one you used in your Caddyfile), since that's needed for your SSL cert renewal.

Next, create the service file for Ghost at /lib/systemd/system/ghost.service. It should contain:

  ExecStart=/usr/bin/npm start --production  
  ExecStop=/usr/bin/npm stop --production  

Finally, you need to modify your Ghost config, which is at /var/www/ghost/config.js. This is a JavaScript file, and you'll need to modify the section where it has the config for your production configuration. You should only need to change the value for the url.

  config = {  
      // ### Production
      // When running Ghost in the wild, use the production environment.
      // Configure your URL and mail settings here
      production: {
          url: 'https://<YOUR-DOMAIN>',
          mail: {},
          database: {
              client: 'sqlite3',
              connection: {
                  filename: path.join(__dirname, '/content/data/ghost.db')
              debug: false
          server: {
              host: '',
              port: '2368'

As usual, change <YOUR-DOMAIN> to the one you bought.

After that, you're ready to start your services. Start Ghost by running service ghost start, and then start Caddy by running service caddy start. After that, you can go to your domain, and everything should be working. If you get any errors, the best place to start is in the logs for your services, so run journalctl -u caddy or journalctl -u ghost to see the output.

If you've done everything correctly, your domain will be all setup with Ghost, and served over HTTPS. Congrats on the new blog!

Enjoyed this article? Found a bug? Using Upstart instead of Systemd? Whatever your needs, feel free to email me at ben at bentranter dot io.