
When you publish a website or put an application behind a reverse proxy, configuring HTTPS can take longer than installing the web server. Caddy handles certificate issuance, renewal, and HTTP-to-HTTPS redirects automatically for most deployments.
Once your domain points to the server, a short Caddyfile is often enough to serve the site over HTTP/2 and HTTP/3. This guide explains how to install Caddy on Ubuntu 26.04 from the official APT repository, serve a static site, configure a reverse proxy, and manage the service with systemd.
Quick Reference
| Task | Command |
|---|---|
| Install Caddy | sudo apt install caddy |
| Edit configuration | sudo nano /etc/caddy/Caddyfile |
| Reload configuration | sudo systemctl reload caddy |
| Restart Caddy | sudo systemctl restart caddy |
| Check status | sudo systemctl status caddy |
| Validate Caddyfile | sudo caddy validate --config /etc/caddy/Caddyfile |
| Format Caddyfile | sudo caddy fmt --overwrite /etc/caddy/Caddyfile |
| View logs | sudo journalctl -u caddy -f |
Prerequisites
Before you start, make sure you have:
- An Ubuntu 26.04 server with a user that has
sudoaccess. - A domain name with an A or AAAA record pointing to the server if you want a publicly trusted HTTPS certificate.
- TCP ports 80 and 443 open in the server firewall and any provider-level firewall.
If you use UFW
, allow HTTP and HTTPS traffic with these commands:
Terminal
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Caddy supports HTTP/3 over UDP. Allow UDP port 443 if you want clients to use it:
Terminal
sudo ufw allow 443/udp
If you only want to test Caddy locally, use an explicit HTTP address such as or a non-standard port such as :8080 in the Caddyfile. Caddy will not activate automatic HTTPS for that site address.
Install Caddy from the Official Repository
The Caddy project maintains an official APT repository on Cloudsmith. We will use it to install the current stable release and receive future Caddy updates through APT.
Install the prerequisite packages:
Terminal
sudo apt update
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
Add the Caddy signing key:
Terminal
curl -1sLf ' \
| sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
Add the Caddy APT repository:
Terminal
curl -1sLf ' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
Make both repository files readable by APT:
Terminal
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
Update the package index and install Caddy:
Terminal
sudo apt update
sudo apt install -y caddy
The package installs the binary at /usr/bin/caddy, creates the caddy system user, adds a default configuration at /etc/caddy/Caddyfile, and installs the caddy.service systemd unit.
Display the installed version:
Terminal
caddy version
Next, confirm that the service is running:
Terminal
sudo systemctl status caddy
The status output should show active (running). Press q to exit the status screen.
You can also request the default site from the server:
Terminal
curl -I
A working installation returns an HTTP 200 OK response and includes Server: Caddy in the headers. You can also open the server’s IP address in a browser to view the default page.
Serve a Static Site
Caddy reads its configuration from /etc/caddy/Caddyfile. The default file serves content from /usr/share/caddy on port 80.
Before changing it, create a backup:
Terminal
sudo cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.backup
Next, open the Caddyfile:
Terminal
sudo nano /etc/caddy/Caddyfile
Replace the contents with the following site block. Change example.com to your domain:
/etc/caddy/Caddyfilecaddy
example.com {
root * /var/www/example.com
file_server
}
The domain on the first line is the site address. Caddy uses it to obtain a certificate, redirect HTTP requests to HTTPS, and serve the site securely. You do not need a separate certificate block or listen directive.
Create the document root and add a test page:
Terminal
sudo mkdir -p /var/www/example.com
echo "<h1>Hello from Caddy</h1>" | sudo tee /var/www/example.com/index.html
sudo chmod -R a+rX /var/www/example.com
The caddy service only needs read access to static site files. Keeping the files owned by your deployment user or by root prevents the web server process from changing them.
Before applying the new configuration, validate it:
Terminal
sudo caddy validate --config /etc/caddy/Caddyfile
If the command does not report an error, reload Caddy:
Terminal
sudo systemctl reload caddy
The reload command applies the new Caddyfile without stopping the service. Caddy then requests a certificate and redirects HTTP traffic to HTTPS. If DNS points to the server and ports 80 and 443 are reachable, certificate issuance usually completes within a few seconds.
Add a Reverse Proxy
Caddy is often used in front of an application running on the same server. For example, if a Node.js or Python application listens on 127.0.0.1:3000, use this Caddyfile:
/etc/caddy/Caddyfilecaddy
app.example.com {
reverse_proxy 127.0.0.1:3000
}
With this configuration, Caddy handles HTTPS and forwards requests to the application. It also passes the incoming Host header, sets the common X-Forwarded-* headers, and supports WebSocket connections without extra directives.
To proxy a subpath, scope the directive:
/etc/caddy/Caddyfilecaddy
example.com {
root * /var/www/example.com
file_server
handle /api/* {
reverse_proxy 127.0.0.1:3000
}
}
Requests under /api/ are sent to the application with the /api prefix unchanged. Other requests continue to use the static file server. If the application expects /users instead of /api/users, replace handle with handle_path to remove the matching prefix before proxying the request.
Validate and reload the configuration after making the change:
Terminal
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
If you also work with Nginx, see our Nginx reverse proxy guide
for the equivalent configuration.
Host Multiple Sites
You can host several domains and subdomains from the same Caddyfile. Add one site block for each hostname:
/etc/caddy/Caddyfilecaddy
example.com {
root * /var/www/example.com
file_server
}
blog.example.com {
root * /var/www/blog
file_server
encode gzip
}
api.example.com {
reverse_proxy 127.0.0.1:8080
}
Caddy obtains and renews a certificate for each eligible hostname. Unlike an Nginx sites-available setup, this configuration does not require a separate symlink step.
As the number of sites grows, you can split the configuration across files with the import directive:
/etc/caddy/Caddyfilecaddy
import /etc/caddy/sites/*.caddy
Create the /etc/caddy/sites directory, then add one .caddy file for each site. Run caddy validate after adding or removing an imported file.
Validate and Format the Caddyfile
Before every reload, validate the Caddyfile:
Terminal
sudo caddy validate --config /etc/caddy/Caddyfile
Validation parses the file and provisions its modules without starting another server. If Caddy finds a syntax or configuration error, it reports the affected line or directive.
Caddy also includes a formatter for indentation and spacing:
Terminal
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
The --overwrite option replaces the file with the formatted version. Review the file or keep it in version control if you want to inspect formatting changes before deployment.
Manage Caddy with systemd
The package runs Caddy as a systemd
service named caddy.service. Use these commands to control it:
Terminal
sudo systemctl start caddy
sudo systemctl stop caddy
sudo systemctl restart caddy
sudo systemctl reload caddy
sudo systemctl status caddy
For Caddyfile changes, use reload instead of restart. Reloading applies the new configuration through Caddy’s admin API without interrupting active connections.
The package normally enables the service during installation. If it was disabled, enable it again with:
Terminal
sudo systemctl enable caddy
View Caddy Logs
Caddy writes its runtime and service logs to the systemd journal. Follow new entries with journalctl
:
Terminal
sudo journalctl -u caddy -f
Access logging is separate and is not enabled by default. To write JSON access logs to a file, add a log directive to the site block:
/etc/caddy/Caddyfilecaddy
example.com {
root * /var/www/example.com
file_server
log {
output file /var/log/caddy/access.log
format json
}
}
Create the log directory once with the correct ownership:
Terminal
sudo mkdir -p /var/log/caddy
sudo chown caddy:caddy /var/log/caddy
Reload Caddy after validating the change. Caddy rotates file-based access logs by default, and JSON output can be read by log collection tools such as Vector, Loki, or Elasticsearch.
Update Caddy
Because Caddy was installed from an APT repository, you can update it with the normal package workflow:
Terminal
sudo apt update
sudo apt install --only-upgrade caddy
The package upgrade keeps your Caddyfile and Caddy data under /var/lib/caddy. After an update, confirm the installed version and service status:
Terminal
caddy version
sudo systemctl status caddy
Troubleshooting
bind: address already in use on port 80 or 443
Another web server (often Apache or a previous Nginx install) is already listening on the port. Stop the conflicting service with sudo systemctl stop apache2 or sudo systemctl stop nginx, then start Caddy.
Certificate issuance fails
Caddy needs inbound access on port 80 for the HTTP challenge or port 443 for the TLS-ALPN challenge. Confirm the firewall allows both, that every configured A or AAAA record points at this server, and that no other proxy intercepts the validation request. The journal log (journalctl -u caddy) shows the exact ACME error.
permission denied reading /var/www/...
Caddy runs as the caddy user, not as root. Files under the document root must be readable, and parent directories must be executable. Run sudo chmod -R a+rX /var/www/example.com, then check each parent directory if the error continues. Avoid giving the service account ownership of files that it only needs to serve.
Caddyfile validates but the site is unreachable
Check that the site block uses the exact hostname requested by the browser. Test DNS with getent hosts example.com, then use curl -v to inspect the connection and response.
HTTP requests are not redirected to HTTPS
Automatic HTTPS is disabled when a site address starts with http:// or contains only an HTTP port. Use example.com rather than in the Caddyfile, validate the configuration, and inspect the journal for certificate errors.
Conclusion
Once Caddy is running, most day-to-day work happens in /etc/caddy/Caddyfile. Validate each change before reloading, and keep ports 80 and 443 reachable so Caddy can issue certificates and redirect visitors to HTTPS.
