In the past, my blog has been running through GitHub Pages, which is extremely quick and easy — but lacks the flexibility and control that I wanted to start experimenting with.

There’s quite a few random tutorials about setting up Caddy online, but I found none of them quite did the job for me (as a bit of a noob.) Or some tutorials showed how to setup Caddy… but not the full process of Digital Ocean + Caddy, which is what I wanted. So here is my explanation on how to do it, in simple terms for any other newbie out there.

Warning: This is all based on my experience, and I am in no way an expert. Some things I say may not be politically correct, but at least helped me understand them, and may help you too.

By the end of tutorial we will have:

  • A Droplet hosted from Digital Ocean
  • Our domain name linked to our droplet
  • A Caddy server running to serve our site
  • An SSL certificate (https) automatically generated for our site
  • Caddy automatically pulling updates from our git repository
  • Caddy running continuously on our Droplet

Why Caddy, and what is it?

Caddy is a web sever — an alternative to something like Apache or nginx. It works great for statically generated websites; perfect for my blog.

What I like about Caddy is how easy it is to get up and running, all your configuration is stored in a simple file, called a Caddyfile, and the big winner… it serves over HTTPS by default. Which means you get that cool little lock 🔒 icon next to your URL, also known as an SSL certificate.

HTTPS Example

While this makes your site appear ‘more legit’, it is also a great security feature — which used to cost hundreds of dollars to get for your site, but can now be done for free and automatically through Let’s Encrypt 🔥. Which is what Caddy uses under the hood.

Why Digital Ocean?

I’d heard the name ‘Digital Ocean’ a bunch of times across my travels through the interwebs, and it seemed to have a pretty good reputation. In my experience, Digital Ocean offers a great pricing model and an extremely good user experience, which for me made them a no-brainer.

Deploying my blog with Caddy on Digital Ocean

Now, let’s go through, step by step how to use Caddy on Digital Ocean. Or at least how I did it for my blog.

I apologise for the length of this post. I may split it into multiple posts in the future, but for now… it is one very long tutorial 😜


Step 1 — Digital Ocean Droplet

First we create a Digital Ocean Droplet, which is essentially setting aside a computer for you.

1.1 — Login or create a Digital Ocean account

Digital Ocean Homepage

If you haven’t created an account before, if use my referral link https://m.do.co/c/0b41a7dd35e8 you will get $10. Which is basically 2 months for free! You will have to enter your credit card details when creating an account, but it only costs 0.7 cents an hour, so it may cost you a total 1 cent by the time you finish setting up, or nothing if you use my referral link. 👌

1.2 – Create a new Droplet

Empty Digital Ocean Dashboard
Create Digital Ocean Droplet
Create Digital Ocean Droplet

We’ll keep pretty much the default values, which in my case:

  • Choose an image: Ubuntu — 16.04.2 x64
  • Choose a size: the $5 a month option will be fine for us
  • Choose a datacenter region: Any will do, in this case I went the default ‘New York – 3’
  • Select additional options: Don’t select any, it’s fine without any of these
  • Add your SSH Keys: We’ll do this bit now…

1.3 — Add an SSH key to Digital Ocean

Digital Ocean have their own explanation for this here. But here is my personal take on it…

What is an SSH Key?

Adding an SSH key, is basically creating a two-part password on your computer — made up of a public and private key. By providing Digital Ocean the public part of our key, it’s like giving them a padlock to use 🔒, knowing that we are the only ones with the key 🔑 to unlock it 🔐.

Creating a new key

Create a new RSA Key Pair in your terminal:

ssh-keygen -t rsa

It will prompt you to enter a file to save it:

Generating public/private rsa key pair.
Enter file in which to save the key (/Users/dwilliames/.ssh/id_rsa):

You can just press enter to create it as a file called id_rsa.

If the file exists, e.g. /Users/dwilliames/.ssh/id_rsa already exists., then you don’t need to overwrite it. Just skip to the next bit.

It will then ask you to enter a passphrase, twice. This is to password protect the key pair — which is optional. To make it simpler later, I didn’t enter a password.

Enter passphrase (empty for no passphrase):
Enter same passphrase again:

You should then see it spit out something like this…

Your identification has been saved in id_rsa.
Your public key has been saved in id_rsa.pub.
The key fingerprint is:
SHA256:O81tDqRFLM2/JWL9rdIKKIloneZd0l2bkv/EShL4rDY dwilliames@Davids-MacBook-Pro.local
The key\'s randomart image is:
+---[RSA 2048]----+
|                 |
|         +       |
|        . =      |
|         + o     |
|        S * = .  |
|   o o o & * O . |
|  o = + B % B.+ .|
| . o . +Eo X.o.. |
|    . ....  =+o  |
+----[SHA256]-----+

Copy the public key

We want to copy the public key to provide it for our Digital Ocean Droplet.

Enter:

cat ~/.ssh/id_rsa.pub

It will spit out something like this…

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDEf1Zl6TcIyaqmwIEtRZaTJTaTFhli8IoIduHZjGSgvJUZkuV/x3BB4tareZX/UqRAz4KMVDij2+55JyHw7lT6ufkyHmgPZAj+2VbsJVuFdtozKZWtYkDveWSoCWo6RVkcJP8ByGZvoxcOJm98bvpKuup+3UEGHJd0wEqpyjY7F/pl5QJB+fZIth//CQmRCOX60X5Mx0ROnk9NW4lIxiGYDW9A3MizFz0zfw3s+DhjUzvngKhlLCLYqB1ZGi6WHjcyJq2nVxMPqsz4otPbO2JMKADQWYP4vXLkVdsafWbGDqKH/zoXEg5q9dO97glME+IaVpdNAYhJra+oefdUdS/T dwilliames@Davids-MacBook-Pro.local

Copy that…

Add the SSH key to the new Droplet

Click the ‘New SSH Key’ button, and paste in our newly copied public key.

Give it a name that will help you remember which computer generated it.

Create Digital Ocean Droplet — add SSH key

1.4 — Finish creating the droplet

Click the Create button. Then it will take about 30 seconds for the droplet to get up and running.

Congrats! 🎉 We have a new droplet. Next time you create one it will be much quicker because you won’t have to create an SSH key again — you can simply use the one you already added.

Newly Created Digital Ocean Droplet

In order to properly run Caddy, we need to use a domain name to link to it. If you don’t have a domain name, I personally recommend Hover. They have a great user experience and support.

2.1 — Assign Nameservers

It’s pretty similar with all Domain Registrars, essentially find a tag or button to edit your ‘Nameservers’. You want to change them to add these 3:

  • ns1.digitalocean.com
  • ns2.digitalocean.com
  • ns3.digitalocean.com
Editing Name Servers

Updating Nameservers on Hover

For more details on connecting with your Domain Registrar, Digital Ocean have a great guide here.

This means that now we have control of updating all the DNS records for this domain name directly within Digital Ocean — now we can point our domain to our droplet server.

2.2 — Point DNS record to Droplet

Signed in to Digital Ocean, go to the Networking tab, then enter in your domain name and click Add Domain.

Add Domain to Digital Ocean

Now to create a DNS record. On the ‘A’ tab, enter ’@’ into Hostname, then select your droplet from the ‘Will direct to’ field, and click Create Record.

Create DNS Record

We’ll also add a CNAME record so that going to ‘www.yourdomain.com’ will direct to ‘yourdomain.com’. Click the ‘CNAME’ tab, enter ‘www’ as the hostname, and ’@’ for ‘is an alias of’. Then click Create Record.

Create CNAME Record

Now going to your domain name should direct us to our Droplet.


Step 3 — Add Caddy to our Droplet

Now we’re going to install Caddy on our new droplet to serve our site.

3.1 — Log into our Droplet

To log into our new droplet, we need to know it’s IP Address, which will be listed alongside it in our Digital Ocean dashboard. Then we ssh into the root of it.

ssh root@DROPLET_IP_ADDRESS
# e.g. ssh root@104.236.224.19

If the SSH key you uploaded wasn’t the default ~/.ssh/id_rsa, you’d probably see something like Permission denied (publickey). then use this command to specify which key to log in with:

ssh root@DROPLET_IP_ADDRESS -i PATH_TO_KEY
# e.g. ssh root@104.236.224.19 -i ~/.ssh/new_rsa

It will say something like this:

The authenticity of host \'104.236.224.19 (104.236.224.19)\' can\'t be established.
ECDSA key fingerprint is SHA256:cH3vfy45IR2gUlspG+2FwezqGla2HHGoLiHtay0y8V8.
Are you sure you want to continue connecting (yes/no)?

Type yes. Don’t just hit enter, actually type ‘yes’ then hit enter

Warning: Permanently added '104.236.224.19' (ECDSA) to the list of known hosts.

Then we should be into our Droplet!!! root@ubuntu-512mb-nyc3-01:~#

Now, whenever you want to get into your droplet, simply type the same ssh root@IP_ADDRESS as before.

3.2 — Install Caddy

When installing Caddy, we probably want to install a couple of plugins, which Caddy refers to as ‘directives’. You can see all the directives at Caddy’s user Guide on the side panel. A bunch are included with the base Caddy installation, but some (marked with an ‘A’ for ‘Add-on’) need to be selected when installing.

One of the directives we want to install is git. This will allow it so that every time we push to our Git repository we want to host, it will automatically pull down the changes.

To install Caddy, with the git add-on:

curl -fsSL https://getcaddy.com | bash -s git
# For any additional add-ons, add them separated by a comma
# e.g. curl -fsSL https://getcaddy.com | bash -s git,cors,jsonp

Now we have Caddy installed, we also need to set it’s permissions to use ports 443 (https) and 80 (http).

setcap cap_net_bind_service=+ep /usr/local/bin/caddy

3.3 — Create a Caddyfile

Now Caddy is installed, we need a Caddyfile to list all the commands for it to run.

Create a Caddyfile, then begin editing it

touch Caddyfile
nano Caddyfile

Let’s create a real simple Caddyfile for now to test that everything works. Enter something like this:

DOMAIN_NAME {
  tls YOUR_EMAIL_ADDRESS
}

# Example for me
# davew.io {
#   tls david@williames.com
# }

To finish editing with nano, type control + x, then y then enter.

Warning: Your email address is used to automatically renew your SSL certificates, so make sure you use yours, or you may not be able to recover your certificates if needed

3.4 — Running Caddy

Now that we have a Caddyfile for Caddy to use, let’s run Caddy and see if everything is working as expected.

First, let’s create a simple webpage to test it with, then run Caddy.

# Create a simple webpage
echo 'Hello, World!' >> ./index.html

# Run Caddy
caddy --conf ./Caddyfile --agree --email YOUR_EMAIL_ADDRESS
# Example for me
# caddy --conf ./Caddyfile --agree --email david@williames.com

It should spit out something like…

Activating privacy features... done.
https://davew.io
http://davew.io
WARNING: File descriptor limit 1024 is too low for production servers. At least 8192 is recommended. Fix with "ulimit -n 8192".

Now going to your domain, you should see ‘Hello, World!’, with an SSL/HTTPS ‘lock’ next to it. 🎉


Step 4 — A more advanced Caddyfile

Now that we have Caddy up and running, it would be ideal for it to automatically pull from our git repository every time we push a change to our master branch. We will do this through a webhook on Github, and the Git directive in our Caddyfile.

Step 4.1 — Creating a webhook

For this demonstration I’ll be showing how to setup the webhook with Github. Where a webhook is basically an API endpoint that we hit every time a push is made do the master branch of our git repository.

Under your repository on Github, click ‘Settings’, then the ‘Webhooks’ tab on the left side. Then click ‘Add webhook’.

Create Webhook

Set the ‘Payload URL’ to be https://yourdomain.com/webhook and make sure you set ‘Content Type’ to be ‘application/json’. You can leave ‘secret’ empty if you like, and click ‘Add webhook’.

Step 4.2 — Add Git to our Caddyfile

Now that we have told Github to send our webhook to https://yourdomain.com/webhook, we need to actually add that webhook path to our Caddyfile via the Git directive.

DOMAIN_NAME {
  tls YOUR_EMAIL_ADDRESS
  root /root/repo_name
  git {
    repo git@github.com:username/repo_name.git
    key /root/.ssh/id_rsa
    hook /webhook
  }
}

# Example for me
# davew.io {
#   tls david@williames.com
#   root /root/blog
#   git {
#     repo git@github.com:DWilliames/blog.git
#     key /root/.ssh/id_rsa
#     hook /webhook
#   }
# }

root is where the files will be served from when someone hits our domain name. Since we will be cloning our repo, it makes sense to set our path within the repo directory — so the same name as the repository.

In order for the Droplet to be able to have access to our repository, we need to also generate an SSH key on the Droplet and upload it to Github. Similar to adding the SSH key to Digital Ocean in Step 1.3.

To recap, within your Droplet:

  1. Generate an SSH key ssh-keygen -t rsa, and keep the default file path and empty passwords.
  2. Print out the public key for it cat ~/.ssh/id_rsa.pub, and copy it
  3. Paste the key in Github > Settings > SSH and GPG Keys > New SSH Key

Now run caddy to see if it works:

caddy --conf ./Caddyfile --agree --email YOUR_EMAIL_ADDRESS
# Example for me
# caddy --conf ./Caddyfile --agree --email david@williames.com

It should spit out something like:

Activating privacy features... done.
Cloning into '/root/blog'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.
Checking connectivity... done.
2017/03/21 12:04:56 git@github.com:DWilliames/blog.git pulled.
https://davew.io
http://davew.io
WARNING: File descriptor limit 1024 is too low for production servers. At least 8192 is recommended. Fix with "ulimit -n 8192".

And if we have set up our webhook properly — in a new terminal tab, make a change to your repo and push the changes. You should see the commands in your Droplet terminal tab spit out something like:

2017/03/21 12:05:53 Received pull notification for the tracking branch, updating...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:DWilliames/blog
 * branch            master     -> FETCH_HEAD
   9c25a86..ee9dae9  master     -> origin/master
Updating 9c25a86..ee9dae9
Fast-forward
 index.html | 1 +
 1 file changed, 1 insertion(+)
2017/03/21 12:05:53 git@github.com:DWilliames/blog.git pulled.

…and going to your Domain, should automatically reflect the new changes!! 🎉


Step 5 — Running Caddy continuously

At the moment, we can start Caddy with our caddy command. However, whenever we leave our SSH session or cancel the command, our server also stops. We want Caddy to continuously run in the background, and restart itself if something goes wrong.

We do this through Linux’s ‘Systemd’. Where we create a service file, which will handle all the running and restarting of Caddy.

To do this, first let’s create a Systemd service file called caddy.service, and begin editing it.

# Create service file
touch /etc/systemd/system/caddy.service
# Begin editing file
nano /etc/systemd/system/caddy.service

Paste the following code into the file:

[Unit]
Description=Caddy Web Server
Documentation=https://caddyserver.com/docs
After=network.target

[Service]
User=root
StartLimitInterval=86400
StartLimitBurst=5
LimitNOFILE=16535
ExecStart=/usr/local/bin/caddy --agree --email 'YOUR_EMAIL_ADDRESS' --conf=/root/repo_name/Caddyfile --pidfile=/var/run/caddy/$
PIDFile=/var/run/caddy/caddy.pid
Restart=on-failure

[Install]
WantedBy=multi-user.target

This file basically says which command needs to be run on startup, or reboot; and it will automatically attempt to restart this daemon service whenever a failure occurs.

Anytime we create or update a service file, we need to call systemctl daemon-reload. Remember that if you ever update caddy.service in the future.

Then we want to run the service with systemctl start caddy.service. Here’s a few other helpful commands:

# Reload the background Services
systemctl daemon-reload

# Restart, start and stop the Caddy service
systemctl restart caddy.service
systemctl start caddy.service
systemctl stop caddy.service

# Check the current status of the Service.
# e.g. Is it active?
systemctl status caddy.service

# View the recent logs of the Service
journalctl -xe

Check the status of the service systemctl status caddy.service, it should say ‘active’. If it doesn’t, check the recent logs of the service, journalctl -xe, to see if you can figure out what your problem is.

If the status is ‘active’ — then you are good to go!! Go to your domain, and check that it is running well.

Now test pushing changes to your repository and after about 20 seconds, the changes should automatically reflect on your site. 🎉

It is finished

Finally, we got there!