Caddy is a reverse proxy server written in Go. It is a completely free, open-source project with an Apache 2.0 license.
Caddy supports HTTP/2, HTTP, and HTTPS, and allows for automatic obtaining and renewing of Let’s Encrypt certificates. It is cross-platform and supports various processor architectures. Additionally, you can run Caddy in a Docker container.
Caddy Key Features
In this guide, we will use a cloud server with Ubuntu 22.04 and minimal configuration.
Connect to the server and install Caddy according to the official documentation:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
Check the service status:
systemctl status caddy
Check the version:
caddy version
# Output:v2.7.4
To verify, open the page in your browser using the host's IP address.
Next, follow the step-by-step configuration recommendations provided by Caddy.
Create a third-level domain name. For this example, we will use caddy.example.com
.
Set the A record to point to the public IP address of the created server.
Create your own index.html
at the specified path /var/www/html/
. Add an image, for example, image.jpg
:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Header</title>
</head>
<body>
<header>
<h1>Simple index file for demo</h1>
</header>
<main>
<h2>Caddy checklist</h2>
<p>Let's configure Caddy following the instructions:</p>
<ul>
<li>Set the A record</li>
<li>Upload files</li>
<li>Edit Caddyfile</li>
<li>Reload configuration</li>
<li>Visit the site</li>
</ul>
<img src="image.jpg" alt="new_image">
</main>
</body>
</html>
Install nano:
sudo apt install nano
Open the configuration file for editing:
nano /etc/caddy/Caddyfile
Remove all the content and the following lines:
caddy.example.com {
root * /var/www/html
file_server
}
Restart Caddy using the command:
systemctl reload caddy
Open the page using your domain: caddy.example.com
.
The page will load immediately over HTTPS with a trusted certificate — all of this in just four lines of configuration, keeping everything minimal, clean, and understandable.
Suppose you need to isolate the service and redirect traffic. Using containers without installing additional packages or dependencies is very convenient, and starting everything with a single command. Therefore, let’s stop the Caddy service and install Docker:
systemctl stop caddy
apt remove caddy
apt install docker.io docker-compose
Now we repeat the same steps as before, but now in the container.
Create a directory at /srv/caddy
and copy the static files and configuration files there:
mkdir -p /srv/caddy/site
cp -r /var/www/html /srv/caddy/site
cp /etc/caddy/Caddyfile /srv/caddy/Caddyfile
Create a docker-compose.yml
file with the following configuration:
version: '3.8'
services:
caddy:
image: caddy:2.7.4
restart: always
ports:
- 80:80
- 443:443
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./site:/var/www/html
Here, we use volumes to transfer the files to the familiar directories, ensuring nothing is saved in the configuration.
After this, run:
docker-compose up -d
Let's look at how we can further secure the page using BasicAuth with just a few configuration lines.
Edit the Caddyfile as follows, adding basicauth * {login-password}
:
caddy.example.com {
basicauth * {
image $2y$10$v8t9CqkLFEon3UTYKUsRs.8zhMMLFX5.9WyDERzd7ESRT75PICkiW
}
root * /var/www/html
file_server
}
You can generate the login and password in the console or use online .htpasswd
generators (the default algorithm is bcrypt
).
Then, restart with:
docker-compose restart
Now, when you open the page, authentication windows will appear.
Next, in docker-compose, let’s use a service with a web interface, such as Grafana.
version: '3.8'
caddy:
image: caddy:2.7.4
restart: always
ports:
- 80:80
- 443:443
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./site:/var/www/html
grafana:
image: grafana/grafana:10.0.5-ubuntu
container_name: grafana
# ports:
# - 3000:3000
environment:
GF_SECURITY_ADMIN_USER: 'admin'
GF_SECURITY_ADMIN_PASSWORD: 'admin'
The ports are commented out because both Grafana and Caddy are connected to the same group and can communicate by service names, so external port forwarding is not needed. If you need to access the service not only by the domain name but also by the local IP address and port, you will need to uncomment those lines.
For the Grafana service, create another third-level domain name in DNS, for example, test.example.com
, and add it to the Caddyfile configuration:
caddy.example.com {
root * /var/www/html
file_server
}
test.example.com {
reverse_proxy grafana:3000
}
So far we have the following structure:
In the site directory, there are two files:
index.html
(the main page of the site).
image.jpg
.
The docker-compose.yml
file starts two services:
Caddy for proxying and obtaining certificates.
Grafana as a separate web application.
In the Caddyfile (the configuration file), using volumes, we share the internal container to /etc/caddy/Caddyfile
.
When accessing caddy.example.com
, we get static files from the site directory. Accessing test.example.com
forwards requests to Grafana.
To check that the new domain name is resolving correctly on this host, verify the DNS records:
nslookup test.example.com
If everything works, you can check it in a browser. Go to test.example.com
— it should open the Grafana login page.
In this article, we provided simple examples, but you can also read in the documentation about load balancing to distribute traffic across different hosts, adding health checks to verify the availability of hosts, how to trim, add, or modify headers in requests, etc.
We don't dive too deeply into the configuration, as Caddy is primarily about simplicity. The configurations shown in the article are very convenient and clear, and you might think there can't be anything simpler. But it turns out, there is.
For example, if on the same cloud machine we needed to deploy just Grafana, obtain a certificate, and secure it behind a proxy server, the Caddyfile configuration would look like this:
test.example.com
reverse_proxy grafana:3000
With just two lines in the source, we get a full-fledged proxy server, with HTTP-to-HTTPS redirection and automatic SSL certificate generation. This is exactly why we like Caddy.