How to Install Caddy Web Server on Ubuntu

Install Caddy on Ubuntu from the official Cloudsmith APT repo: add the GPG key, apt install caddy 2.11.4, verify systemd, serve a static site with a Caddyfile, open UFW ports 80 and 443, and enable automatic HTTPS for public domains or localhost.

Published

Updated

Read time 8 min read

Reviewed byDeepak Prasad

Install Caddy web server on Ubuntu banner with HTTPS lock icon and Caddy green accent

Caddy is an open-source web server written in Go. It is known for a short Caddyfile config format, built-in reverse proxy and static file serving, and automatic HTTPS—Caddy obtains and renews TLS certificates without a separate Certbot cron job. That makes it a practical alternative to nginx or Apache when you want TLS on by default.

This guide shows how to install Caddy on Ubuntu from the official APT repository, confirm the systemd service, serve a demo static site, and understand when automatic HTTPS kicks in. I ran every step on Ubuntu 25.04 and kept real terminal output below.

Tested on: Ubuntu 25.04 (Plucky Puffin); kernel 6.14.0-37-generic.

NOTE
The Caddy .deb package enables and starts caddy.service immediately. On my host nginx was already listening on port 80, so Caddy failed until I stopped nginx. Free port 80 (and 443 for HTTPS) before you expect the welcome page from Caddy—not from another server.

Quick command summary

Task Command
Install prerequisites sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
Add Caddy GPG key curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
Add Caddy APT source curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
Fix key/list permissions sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg /etc/apt/sources.list.d/caddy-stable.list
Install Caddy sudo apt update && sudo apt install -y caddy
Check version caddy version
Service status systemctl status caddy
Validate Caddyfile sudo caddy validate --config /etc/caddy/Caddyfile
Apply config sudo systemctl reload caddy
Test HTTP locally curl -sI http://127.0.0.1/
Allow firewall (UFW) sudo ufw allow 80/tcp && sudo ufw allow 443/tcp
Remove Caddy sudo apt purge -y caddy

Prerequisites

  • Ubuntu 22.04 LTS, 24.04 LTS, or newer (25.04 tested here) on amd64 (official .deb targets Debian/Ubuntu).
  • A user with sudo privileges (add to sudo group if needed).
  • Outbound HTTPS to dl.cloudsmith.io and, for public sites, DNS A/AAAA records pointing at your server.
  • Ports 80 and 443 reachable from the internet when you want Let's Encrypt certificates (firewall and cloud security groups).
  • For local HTTPS on a desktop, a browser and optional caddy trust to install Caddy's local CA root.

Step 1: Add the official Caddy APT repository

Caddy is not in the default Ubuntu archive. Follow the Caddy install docs for Debian/Ubuntu.

Install transport helpers and cURL:

bash
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl

Import the stable repository signing key and register the source list:

bash
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 chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list

The chmod o+r lines match upstream docs—without them, unprivileged apt may not read the keyring on some setups.

Refresh indexes and install:

bash
sudo apt update
sudo apt install -y caddy

On Ubuntu 25.04 the tail of apt install looked like:

text
Selecting previously unselected package caddy.
Preparing to unpack .../caddy_2.11.4_amd64.deb ...
Unpacking caddy (2.11.4) ...
Setting up caddy (2.11.4) ...
Created symlink '/etc/systemd/system/multi-user.target.wants/caddy.service' → '/usr/lib/systemd/system/caddy.service'.
Processing triggers for man-db (2.13.0-1) ...

Confirm the binary:

bash
caddy version
text
v2.11.4 h1:XKxkMTgNSizEvKG6QHue6cAsFOteU2qA61w2tKkCWi0=
HINT
For beta or release-candidate builds, swap stable for testing in the Cloudsmith URLs (caddy-testing-archive-keyring.gpg, caddy-testing.list). Most production servers should stay on stable.

Step 2: Verify the systemd service

The Debian package registers caddy.service and enables it at boot. Check status:

bash
systemctl is-enabled caddy
systemctl is-active caddy
sudo systemctl status caddy --no-pager

When port 80 is free, you should see active (running):

text
● caddy.service - Caddy
     Loaded: loaded (/usr/lib/systemd/system/caddy.service; enabled; preset: enabled)
     Active: active (running) ...
   Main PID: ... (caddy)
             └─ ... /usr/bin/caddy run --environ --config /etc/caddy/Caddyfile

Port 80 already in use

This one caught me: nginx was bound to :80, so Caddy exited immediately:

text
Status: "loading new config: http app module: start: listening on :80: listen tcp :80: bind: address already in use"
Active: failed (Result: exit-code)

Port conflicts and wrong listen IPs are the usual causes — see Cannot assign requested address for bind-address checks across Nginx, Apache, and other services.

Until I fixed it, curl http://127.0.0.1/ still returned Server: nginx. Free the port:

bash
sudo systemctl stop nginx    # or apache2, or whatever holds :80
sudo systemctl disable nginx # optional—only if Caddy replaces it
sudo systemctl start caddy

Or run Caddy on another port while testing:

caddyfile
:8080 {
	root * /usr/share/caddy
	file_server
}

Step 3: Default welcome page and Caddyfile

The package ships a starter Caddyfile at /etc/caddy/Caddyfile:

caddyfile
:80 {
	root * /usr/share/caddy
	file_server
}

Default static files live in /usr/share/caddy/ (including index.html titled Caddy works!). The caddy system user owns runtime data under /var/lib/caddy:

bash
id caddy
text
uid=994(caddy) gid=980(caddy) groups=980(caddy),33(www-data)

Test HTTP once Caddy owns port 80:

bash
curl -sI http://127.0.0.1/ | head -6
curl -s http://127.0.0.1/ | head -4
text
HTTP/1.1 200 OK
Server: Caddy
...
<!DOCTYPE html>
<html>
<head>
	<title>Caddy works!</title>

Step 4: Serve your own static site

Create a site root and hand ownership to the Caddy user (not www-data):

bash
sudo mkdir -p /var/www/caddy-demo
sudo tee /var/www/caddy-demo/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head><title>Caddy demo</title></head>
<body><h1>Caddy demo site on Ubuntu</h1></body>
</html>
EOF
sudo chown -R caddy:caddy /var/www/caddy-demo

Edit /etc/caddy/Caddyfile (back up first with sudo cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.bak):

caddyfile
:80 {
	root * /var/www/caddy-demo
	file_server
}

Validate before reload—this catches syntax errors without a blind restart:

bash
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

Validation on my host:

text
{"level":"info","msg":"using config from file","file":"/etc/caddy/Caddyfile"}
Valid configuration

Fetch the page:

bash
curl -s http://127.0.0.1/
text
<!DOCTYPE html>
<html>
<head><title>Caddy demo</title></head>
<body><h1>Caddy demo site on Ubuntu</h1></body>
</html>

Public domain with automatic HTTPS

Replace :80 with your domain when DNS A (and AAAA if you use IPv6) records point at this server. Caddy requests Let's Encrypt certificates automatically—no tls email line is required on current Caddy 2.x for the default public CA path:

caddyfile
example.com {
	root * /var/www/caddy-demo
	file_server
}

Ensure ports 80 and 443 are open on the host and upstream firewall. First request may take a few seconds while ACME completes.

Reverse proxy (optional)

To front an app on another port:

caddyfile
example.com {
	reverse_proxy localhost:8080
}

Reload after caddy validate the same way as for static sites.


Step 5: HTTPS on localhost (desktop / lab)

For local development, use localhost as the site address. Caddy enables HTTPS with an internal CA:

caddyfile
localhost {
	root * /var/www/caddy-demo
	file_server
}

After sudo caddy validate and sudo systemctl reload caddy, test HTTPS. If your shell sets HTTPS_PROXY, bypass it for loopback:

bash
curl --noproxy '*' -skI https://127.0.0.1/ | head -6
text
HTTP/2 200
server: Caddy
content-type: text/html; charset=utf-8

On a graphical Ubuntu desktop, run caddy trust once so the local CA root is trusted system-wide (see Caddy automatic HTTPS docs).


Step 6: Firewall (UFW)

When UFW is enabled, allow web traffic:

bash
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload

Keep SSH (port 22) allowed before ufw enable on remote servers. UFW was inactive on my test VM, so I skipped enabling it.


Uninstall

bash
sudo systemctl stop caddy
sudo apt purge -y caddy
sudo rm -f /etc/apt/sources.list.d/caddy-stable.list
sudo rm -f /usr/share/keyrings/caddy-stable-archive-keyring.gpg

Remove /etc/caddy, /var/lib/caddy, and site directories when you no longer need configs or cached certificates.


Troubleshooting

Symptom Likely cause Fix
address already in use on :80 nginx, Apache, or another process on 80/443 sudo ss -tlnp | grep ':80'; stop the conflicting service or change Caddy's listen address
curl shows wrong Server header Traffic hits another web server Same as above; confirm systemctl status caddy is active
HTTPS fails for public domain DNS not pointing here, or ports blocked Fix A/AAAA records; open 80 and 443 on UFW and cloud SG
Valid configuration but site unchanged Forgot reload sudo systemctl reload caddy after edits
Permission denied reading site files Wrong ownership on web root sudo chown -R caddy:caddy /var/www/yoursite
curl to localhost returns proxy 403 HTTPS_PROXY/HTTP_PROXY set curl --noproxy '*' for 127.0.0.1 tests
Certificate errors on localhost Local CA not trusted Run caddy trust on desktop; accept warning in browser for quick tests

Watch logs after changes:

bash
journalctl -u caddy -f --since "10 min ago"

References


Summary

On Ubuntu, install Caddy from the official Cloudsmith stable repository—not the default archive—using the curl + gpg + apt install caddy flow from caddyserver.com/docs/install. The package delivered Caddy 2.11.4 on Ubuntu 25.04, enabled systemd, and served the default page from /usr/share/caddy via /etc/caddy/Caddyfile.

Point your own site root at /var/www/..., chown to caddy:caddy, run caddy validate, then systemctl reload caddy. Use a real domain name in the Caddyfile for Let's Encrypt HTTPS on the public internet, or localhost for local TLS. If the service fails right after install, check whether port 80 is already taken—that was the blocker on my test host until nginx was stopped.


Frequently Asked Questions

1. How do I install Caddy on Ubuntu?

Add the official Caddy stable repository from dl.cloudsmith.io with curl and gpg, run sudo apt update, then sudo apt install caddy. The package installs /usr/bin/caddy, enables the caddy systemd unit, and drops a default Caddyfile under /etc/caddy/.

2. Is Caddy available in the default Ubuntu apt repository?

No. Ubuntu archive does not ship current Caddy builds. Use the official Debian/Ubuntu instructions on caddyserver.com/docs/install, which point to the Cloudsmith caddy/stable repository.

3. What version of Caddy installs on Ubuntu 25.04?

The stable Cloudsmith repo delivered caddy 2.11.4 on Ubuntu 25.04 during testing. Run caddy version after install to see the exact build on your machine.

4. Does Caddy start automatically after apt install?

Yes. The Debian package enables and starts the caddy systemd service. Check with systemctl status caddy. If the service fails with address already in use on port 80, another web server such as nginx or Apache is already bound—stop it or change the site address in the Caddyfile.

5. How does Caddy automatic HTTPS work?

When the Caddyfile site address is a public domain name with DNS pointing at the server, Caddy obtains and renews Let's Encrypt certificates with no separate certbot step. For localhost it provisions a local CA and HTTPS on port 443; run caddy trust on a desktop to install the local root if your browser warns.

6. Where is the Caddy configuration file on Ubuntu?

/etc/caddy/Caddyfile is the main config. Validate changes with sudo caddy validate --config /etc/caddy/Caddyfile, then apply with sudo systemctl reload caddy. The admin API listens on localhost:2019 by default.

7. Which user does Caddy run as on Ubuntu?

The package creates a caddy system user and group. Site directories should be owned by caddy:caddy (the caddy user is also in the www-data group). Do not chown to www-data unless you deliberately align permissions with another stack.

8. How do I uninstall Caddy from Ubuntu?

Run sudo apt purge caddy, remove /etc/apt/sources.list.d/caddy-stable.list and /usr/share/keyrings/caddy-stable-archive-keyring.gpg if you no longer want updates, and delete /etc/caddy and /var/lib/caddy when you do not need configs or cached certificates.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …