The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.
Introduction
Caddy is a web server designed around simplicity and security that comes with a number of features that are useful for hosting websites. For example, it can automatically obtain and manage TLS certificates from Let’s Encrypt to enable HTTPS, and includes support for HTTP/2. HTTPS is a system for securing traffic between your users and your server, and is quickly becoming a basic expectation of any website running in production — without it, Chrome and Firefox will warn that your website is “Not Secure” if users try to submit login information.
Previously, the recommended method for installing Caddy was to download pre-built binaries from the Caddy project website. However, changes in how Caddy’s licensing works means that you’re no longer allowed to use these pre-built binaries for commercial purposes unless you pay a license fee, even if you’re just using Caddy internally within a business. Luckily, the Caddy source code is still fully open-source and you can build Caddy yourself to avoid running into licensing issues.
In this tutorial, you’ll build Caddy from source and use it to host a website secured with HTTPS. This entails compiling it, configuring it using a Caddyfile
and installing plugins. In the end, you’ll learn how to upgrade your installation when a new version is released.
Prerequisites
- An Ubuntu 18.04 server with root privileges, and a secondary, non-root account. You can set this up by following our Initial Server Setup Guide for Ubuntu 18.04. For this tutorial the non-root user is
sammy
. - A fully registered domain name. This tutorial will use
your_domain
throughout. You can purchase a domain name on Namecheap, get one for free on Freenom, or use the domain registrar of your choice. - An A DNS record with
your_domain
pointing to your server’s public IP address. You can follow this introduction to DigitalOcean DNS for details on how to add them. - The Go language toolchain installed on your server. Follow our guide on How To Install Go and Set Up a Local Programming Environment on Ubuntu 18.04 to set up Go. You don’t need to create any example projects.
- A personal access token (API key) with read and write permissions for your DigitalOcean account. Visit How to Create a Personal Access Token to create one.
Step 1 — Building Caddy
In this step, you’ll build Caddy from source with the ability to later add plugins, all without changing Caddy’s source code.
For the purposes of this tutorial, you’ll store the source code under ~/caddy
. Create that directory by running the following command:
- mkdir ~/caddy
Navigate to it:
- cd ~/caddy
You’ll store the source code for running and customizing Caddy in a file named caddy.go
. Create it using the following command:
- nano caddy.go
Add the following lines:
package main
import (
"github.com/caddyserver/caddy/caddy/caddymain"
)
func main() {
// caddymain.EnableTelemetry = false
caddymain.Run()
}
This code imports Caddy directly from Github (using Git) and starts it from the entrance main
function. If you wish to enable telemetry, uncomment the caddymain.EnableTelemetry
line and set the value to true
. When you are done, save and close the file.
For caddy.go
to be able to use the imported dependencies, you’ll need to initialize it as a module:
- go mod init caddy
Outputgo: creating new go.mod: module caddy
At this point, you’re all set to build the stock version of Caddy from the above source code by running:
- go install
There will be a lot of output, detailing what libraries Go downloaded as dependencies necessary for compiling. The resulting executable is stored under $GOPATH/bin
, as explained in the prerequisites.
When it finishes, try running Caddy:
- caddy
You’ll see output similar to the following:
OutputActivating privacy features... done.
Serving HTTP on port 2015
http://:2015
WARNING: File descriptor limit 1024 is too low for production servers. At least 8192 is recommended. Fix with `ulimit -n 8192`.
This means that Caddy started successfully, and is available on the 2015
port. You can ignore the warning message, because that limit will be adjusted in later steps without your intervention. To exit, press CTRL + C
.
You have now built and executed Caddy. In the next step, you’ll install Caddy as a service so that it starts automatically at boot, and then adjust its ownership and permissions settings to ensure the server’s security.
Step 2 — Installing Caddy
Now that you’ve verified you’re able to build and run Caddy, it’s time to configure a systemd service so that Caddy can be launched automatically on system startup. To understand more about systemd, visit our Systemd Essentials tutorial.
To begin, move the Caddy binary to /usr/local/bin
, the standard location for binaries that are not managed by Ubuntu’s package manager and aren’t key to system operation:
- sudo mv $GOPATH/bin/caddy /usr/local/bin/
Next, change ownership of the Caddy binary over to the root user:
- sudo chown root:root /usr/local/bin/caddy
This will prevent other accounts from modifying the executable. However, even while the root user will own Caddy, it’s advised to execute it only using other, non-root accounts present on the system. This makes sure that in the event of Caddy (or another program) being compromised, the attacker won’t be able to modify the binary, or execute commands as root.
Next, set the binary file’s permissions to 755
— this gives root full read/write/execute permissions for the file, while other users will only be able to read and execute it:
- sudo chmod 755 /usr/local/bin/caddy
Since the Caddy process will not be running as root, Linux will prevent it from binding to ports 80
and 443
(the standard ports for HTTP and HTTPS, respectively), as these are privileged operations. In order to be easily accessible at your domain, Caddy needs to be bound to one of these ports, depending on the protocol. Otherwise, you would need to add a specific port number to the domain URL in your browser to view the content it will serve.
To allow Caddy to bind to low ports without running as root, run the following command:
- sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/caddy
The setcap
utility sets file capabilities. In this command it assigns the CAP_NET_BIND_SERVICE
capability to the Caddy binary, which allows an executable to bind to a port lower than 1024.
You have now finished setting up the Caddy binary, and are ready to start writing Caddy configuration. Create a directory where you’ll store Caddy’s configuration files by running the following command:
- sudo mkdir /etc/caddy
Then, set the correct user and group permissions for it:
- sudo chown -R root:www-data /etc/caddy
Setting the user as root and the group as www-data ensures that Caddy will have read and write access to the folder (via the www-data group) and that only the superuser account will have the same rights to read and modify. www-data is the default user and group for web servers on Ubuntu.
In a later step, you’ll enable automatic TLS certificate provisioning from Let’s Encrypt. In preparation for that, make a directory to store any TLS certificates that Caddy will obtain and give it the same ownership rules as the /etc/caddy
directory:
- sudo mkdir /etc/ssl/caddy
- sudo chown -R root:www-data /etc/ssl/caddy
Caddy must be able to write certificates to this directory and read from it in order to encrypt requests. For this reason, modify the permissions for the /etc/ssl/caddy
directory so that it’s only accessible by root and www-data:
- sudo chmod 0770 /etc/ssl/caddy
Next, create a directory to store the files that Caddy will host:
- sudo mkdir /var/www
Then, set the directory’s owner and group to www-data:
- sudo chown www-data:www-data /var/www
Caddy reads its configuration from a file called Caddyfile
, stored under /etc/caddy
. Create the file on disk by running:
- sudo touch /etc/caddy/Caddyfile
To install the Caddy service, download the systemd unit file from the Caddy Github repository to /etc/systemd/system
by running:
- sudo sh -c 'curl https://raw.githubusercontent.com/caddyserver/caddy/master/dist/init/linux-systemd/caddy.service > /etc/systemd/system/caddy.service'
Modify the service file’s permissions so it can only be modified by its owner, root:
- sudo chmod 644 /etc/systemd/system/caddy.service
Then, reload systemd to detect the Caddy service:
- sudo systemctl daemon-reload
Check whether systemd has detected the Caddy service by running systemctl status
:
- sudo systemctl status caddy
You’ll see output similar to:
Output● caddy.service - Caddy HTTP/2 web server
Loaded: loaded (/etc/systemd/system/caddy.service; disabled; vendor preset: e
Active: inactive (dead)
Docs: https://caddyserver.com/docs
If you see this same output, then the new service was correctly detected by systemd.
As part of the initial server setup prerequisite, you enabled ufw
, the uncomplicated firewall, and allowed SSH connections. For Caddy to be able to serve HTTP and HTTPS traffic from your server, you’ll need to allow them in ufw
by running the following command:
- sudo ufw allow proto tcp from any to any port 80,443
The output will be:
OutputRule added
Rule added (v6)
Use ufw status
to check whether your changes worked:
- sudo ufw status
You’ll see the following output:
OutputStatus: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
80,443/tcp ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
80,443/tcp (v6) ALLOW Anywhere (v6)
Your installation of Caddy is now complete, but it isn’t configured to serve anything. In the next step, you’ll configure Caddy to serve files from the /var/www
directory.
Step 3 — Configuring Caddy
In this section, you’ll write basic Caddy configuration for serving static files from your server.
Start by creating a basic HTML file in /var/www
, called index.html
:
- sudo nano /var/www/index.html
Add the following lines:
<!DOCTYPE html>
<html>
<head>
<title>Hello from Caddy!</title>
</head>
<body>
<h1 style="font-family: sans-serif">This page is being served via Caddy</h1>
</body>
</html>
When shown in a web browser, this file will display a heading with the text This page is being served via Caddy. Save and close the file.
Open the Caddyfile
configuration file you created earlier for editing:
- sudo nano /etc/caddy/Caddyfile
Add the following lines:
:80 {
root /var/www
gzip
}
This is a basic Caddy config, and declares that the port 80
of your server should be served with files from /var/www
and compressed using gzip
to reduce page loading times on the client side.
In a majority of cases, Caddy allows you to further customize the config directives. For instance, you can limit gzip
compression to only HTML and PHP files and set the compression level to 6 (1 being the lowest and 9 being the highest) by extending the directive with curly braces and listing sub-directives underneath:
:80 {
root /var/www
gzip {
ext .html .htm .php
level 6
}
}
When you are done, save and close the file.
Caddy has a huge number of different directives for many use cases. For example, the fastcgi
directive could be useful for enabling PHP. The markdown
directive could be used to automatically convert Markdown files to HTML before serving them, which could be useful for creating a simple blog.
To test that everything is working correctly, start the Caddy service:
- sudo systemctl start caddy
Next, run systemctl status
to find information about the status of the Caddy service:
- sudo systemctl status caddy
You’ll see the following:
Output● caddy.service - Caddy HTTP/2 web server
Loaded: loaded (/etc/systemd/system/caddy.service; disabled; vendor preset: enabled)
Active: active (running) since Thu 2020-03-12 11:17:49 UTC; 11s ago
Docs: https://caddyserver.com/docs
Main PID: 3893 (caddy)
Tasks: 7 (limit: 1152)
CGroup: /system.slice/caddy.service
└─3893 /usr/local/bin/caddy -log stdout -log-timestamps=false -agree=true -conf=/etc/caddy/Caddyfile -root=/var/tmp
Mar 12 11:17:49 caddy-article-update systemd[1]: Started Caddy HTTP/2 web server.
Mar 12 11:17:49 caddy-article-update caddy[3893]: [INFO] Caddy version: v1.0.5
Mar 12 11:17:49 caddy-article-update caddy[3893]: Activating privacy features... done.
Mar 12 11:17:49 caddy-article-update caddy[3893]: Serving HTTP on port 80
Mar 12 11:17:49 caddy-article-update caddy[3893]: http://
Mar 12 11:17:49 caddy-article-update caddy[3893]: [INFO] Serving http://
Mar 12 11:17:49 caddy-article-update caddy[3893]: [INFO][cache:0xc00007a7d0] Started certificate maintenance routine
Mar 12 11:17:49 caddy-article-update caddy[3893]: [WARNING] Sending telemetry (attempt 1): Post "https://telemetry.caddyserver.com/v1/update/6a8159c4-3427-42
Mar 12 11:17:57 caddy-article-update caddy[3893]: [WARNING] Sending telemetry (attempt 2): Post "https://telemetry.caddyserver.com/v1/update/6a8159c4-3427-42
...
You can now browse to your server’s IP in a web browser. Your sample web page will display:
You have now configured Caddy to serve static files from your server. In the next step, you’ll extend Caddy’s functionality through the use of plugins.
Step 4 — Using Plugins
Plugins offer a way of changing and extending Caddy’s behavior. Generally, they offer more config directives for you to use, according to your use case. In this section, you’ll add and use plugins by installing the minify
plugin, which removes excess whitespace and tidies up the code that will be sent to the client, further reducing footprint and loading times.
The minify
plugin’s GitHub repository is hacdias/caddy-minify.
Navigate to the directory with the source code you created in step one:
- cd ~/caddy
To add a plugin to Caddy, you’ll need to import it in the caddy.go
file you used to build Caddy. Open caddy.go
for editing:
- nano caddy.go
Import the minify
plugin by adding the highlighted line, like so:
package main
import (
"github.com/caddyserver/caddy/caddy/caddymain"
_ "github.com/hacdias/caddy-minify"
)
func main() {
// caddymain.EnableTelemetry = false
caddymain.Run()
}
Save and close the file.
Some plugins may require some slight configuration tweaks, so be sure to read the documentation for whatever ones you install. You can find a list of popular plugins in the left pane of the Caddy documentation, under Plugins.
Any time you add a new plugin, you have to rebuild Caddy. This is because Go is a compiled programming language, meaning the source code is transformed into machine code before execution. Your change to the import declaration has altered the source code, but won’t affect the binary until it’s compiled.
Use the go install
command to compile Caddy:
- go install
When it finishes, move the generated binary to /usr/local/bin
and set up permissions for the binary like you did previously. You must take these steps every time you rebuild Caddy to ensure its functionality and security:
- sudo mv $GOPATH/bin/caddy /usr/local/bin/
- sudo chown root:root /usr/local/bin/caddy
- sudo chmod 755 /usr/local/bin/caddy
- sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/caddy
To start using the minify
plugin, you’ll need to add the minify
directive to your Caddyfile
. Open it for editing:
- sudo nano /etc/caddy/Caddyfile
Enable the plugin by adding the following line to the configuration block:
:80 {
root /var/www
gzip
minify
}
Now, restart your server using systemctl
:
- sudo systemctl restart caddy
Caddy is now running and will minify any files that it serves, including the index.html
file you created earlier. You can observe the ‘minification’ at work by fetching the contents of your domain using curl
:
- curl http://your_domain
You’ll see the following output. Notice that all unnecessary whitespace has been removed, showing that the minify
plugin is working.
Output<!doctype html><title>Hello from Caddy!</title><h1 style=font-family:sans-serif>This page is being served via Caddy</h1>
In this step, you learned how to extend Caddy with plugins. Next, you’ll enable HTTPS by installing the tls.dns.digitalocean
plugin.
Step 5 — Enabling Automatic TLS with Let’s Encrypt
In this section, you’ll enable automatic Let’s Encrypt certificate provisioning and renewal, using TXT DNS records for verification.
To verify using TXT DNS records, you’ll install a plugin for interfacing with the DigitalOcean API, called tls.dns.digitalocean
. The procedure for installing it is almost identical to how you installed the minify
plugin in the previous step. To begin, open caddy.go
:
- nano caddy.go
Add the plugin’s repository to imports:
package main
import (
"github.com/caddyserver/caddy/caddy/caddymain"
_ "github.com/hacdias/caddy-minify"
_ "github.com/caddyserver/dnsproviders/digitalocean"
)
func main() {
// caddymain.EnableTelemetry = false
caddymain.Run()
}
Compile it by running:
- go install
Ensure Caddy is stopped via systemctl
, then finish installing the plugin by copying the newly built Caddy binary and once more setting its ownership and permissions:
- sudo systemctl stop caddy
- sudo mv $GOPATH/bin/caddy /usr/local/bin/
- sudo chown root:root /usr/local/bin/caddy
- sudo chmod 755 /usr/local/bin/caddy
- sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/caddy
Next, configure Caddy to work with DigitalOcean’s API to set DNS records. Caddy needs to access this token as an environment variable to configure DigitalOcean’s DNS, so you’ll edit its systemd unit file:
- sudo nano /etc/systemd/system/caddy.service
Find the line beginning with Environment=
in the [Service]
section. This line defines the environment variables that should be passed to the Caddy process. Add a space at the end of this line, then add a DO_AUTH_TOKEN
variable, followed by the token you just generated:
[Service]
Restart=on-abnormal
; User and group the process will run as.
User=www-data
Group=www-data
; Letsencrypt-issued certificates will be written to this directory.
Environment=CADDYPATH=/etc/ssl/caddy DO_AUTH_TOKEN=your_token_here
Save and close this file, then reload the systemd daemon as you did earlier to ensure the configuration is updated:
- sudo systemctl daemon-reload
Run systemctl status
to check that your configuration changes were OK:
- sudo systemctl status caddy
The output should look like this:
Output● caddy.service - Caddy HTTP/2 web server
Loaded: loaded (/etc/systemd/system/caddy.service; disabled; vendor preset: enabled)
Active: inactive (dead)
Docs: https://caddyserver.com/docs
...
You’ll need to make a couple of slight changes to your Caddyfile
, so open it up for editing:
- sudo nano /etc/caddy/Caddyfile
Add the highlighted lines to the Caddyfile
, making sure to replace your_domain
with your domain (instead of just the port :80
) and commenting gzip
:
your_domain {
root /var/www
#gzip
minify
tls {
dns digitalocean
}
}
Using a domain rather than just a port for the hostname will cause Caddy to serve requests over HTTPS. The tls
directive configures Caddy’s behavior when using TLS
, and the dns
subdirective specifies that Caddy should use the DNS-01
system, rather than HTTP-01
.
With this, your website is ready to be deployed. Start Caddy with systemctl
and then enable
it, so it runs on boot:
- sudo systemctl start caddy
- sudo systemctl enable caddy
If you browse to your domain, you’ll automatically be redirected to HTTPS, with the same message shown.
Your installation of Caddy is now complete and secured, and you can further customize according to your use case.
If you want to update Caddy when a new version comes out, you’ll need to update the go.mod
file (stored in the same directory), which looks like this:
module caddy
go 1.14
require (
github.com/caddyserver/caddy v1.0.5
github.com/caddyserver/dnsproviders v0.4.0
github.com/hacdias/caddy-minify v1.0.2
)
The highlighted part is the version of Caddy you are using. When a new version is released on Github (see the release tags page), you can replace the existing one in go.mod
with it and compile Caddy according to the first two steps. You can do the same for all imported plugins.
Conclusion
You now have Caddy installed and configured on your server, serving static pages at your desired domain, secured with free Let’s Encrypt TLS certificates.
A good next step would be to find a way of being notified when new versions of Caddy are released. For example, you could use the Atom feed for Caddy releases, or a dedicated service such as dependencies.io.
You can explore Caddy’s documentation for more information on configuring Caddy.
Originally posted on DigitalOcean Community Tutorials
Author: DigitalOcean