How to Install Caddy Web Server on Ubuntu

Deepak Prasad
Tested on Ubuntu 26.04 LTS (Resolute Raccoon)
Also applies to Ubuntu 24.04 LTS using the same official Caddy APT repository workflow
Package caddy 2.11.7 (official Caddy repository)
Ubuntu 26.04 Universe: caddy 2.6.2-14
Ubuntu 24.04 Universe: caddy 2.6.2-6ubuntu0.24.04.3 (current amd64 updates)
Configuration /etc/caddy/Caddyfile
Service caddy.service
Applies to Ubuntu 26.04 LTS and 24.04 LTS
Privilege sudo or root
Scope Install Caddy from the official APT repository on Ubuntu, verify systemd, serve a static site, enable automatic HTTPS for a public domain, optional reverse proxy and local HTTPS, UFW, update, and uninstall. Does not cover Docker, Snap, source builds, PHP-FPM, or multi-site administration.
Related guides UFW command
systemctl command
How to remove software on Ubuntu
Cannot assign requested address
Linux commands cheat sheet
Short on time?Beta Get the gist of this 16-minute read in seconds, then ask follow-up questions

Caddy is a Go web server with a short Caddyfile format and automatic HTTPS: it obtains and renews a TLS certificate (often called an SSL certificate) through ACME, the protocol Caddy uses to request and renew certificates automatically, without a separate Certbot cron job on most sites. That makes it a practical choice when you want HTTPS by default instead of configuring certificate issuance and renewal separately, as is common with other web-server setups.

This walkthrough on Ubuntu 26.04 (same APT steps on 24.04):

  • Installs current stable Caddy from the official Debian/Ubuntu repository
  • Confirms systemd and serves a demo static site
  • Shows when automatic HTTPS applies (public domain vs :80 / localhost)

Install and HTTP checks were re-verified on a fresh Ubuntu 26.04 server. Public HTTPS additionally needs a reachable host (or forwarded ports), a domain pointing at it, and TCP 80 and 443 end to end.

NOTE

Before you start:

  • Run commands on the Ubuntu machine where Caddy will run (local terminal or SSH).
  • No domain required to install Caddy or test HTTP locally.
  • Use 127.0.0.1 or a LAN address when you do not have a public IP.
  • A publicly reachable server and domain are needed only for the public automatic-HTTPS section.
NOTE
The Caddy .deb package enables and starts caddy.service at install time. If nginx, Apache, or another process already listens on port 80 or 443, Caddy fails until you free those ports or change the site address in the Caddyfile.

Quick Answer

Step What happens
Add repository Add Caddy's official stable APT repository (commands below)
Install sudo apt update, then sudo apt install caddy
Verify Check caddy.service with systemctl
Configure Edit /etc/caddy/Caddyfile for your site
Public HTTPS Use your real domain as the site address in the Caddyfile
Firewall Allow HTTP/HTTPS in UFW and any hosting/provider firewall when needed

Caddy on Ubuntu 26.04 and 24.04

Ubuntu provides Caddy in Universe on both releases, but the package revisions differ and both trail the current upstream stable release:

Ubuntu release Ubuntu repository package
Ubuntu 26.04 LTS 2.6.2-14
Ubuntu 24.04 LTS 2.6.2-6ubuntu0.24.04.3 (current amd64 updates/security pocket)

Both are based on the older Caddy 2.6.2 series. This guide uses Caddy’s official Debian/Ubuntu repository because it provides the current stable Caddy release while keeping normal APT upgrades and the packaged caddy.service unit.

The install commands below are the same on 26.04 and 24.04.

Ubuntu Repository vs Official Caddy Repository

Package source Advantage Limitation Recommendation
Ubuntu Universe Ubuntu-managed lifecycle Older Caddy (2.6.2 series) Fine if 2.6.2 meets your needs
Official Caddy repository Current stable Caddy + apt upgrade Adds an external APT source Recommended for this guide

This article teaches only the official repository workflow. Custom third-party Caddy plugins require a custom build (xcaddy); the stock package is enough for static sites, reverse proxy, and automatic HTTPS.


Prerequisites

Required for installation:

  • Ubuntu 26.04 or 24.04 with terminal or SSH access and sudo.
  • Outbound HTTPS to dl.cloudsmith.io for the repository setup.

Required only for public automatic HTTPS:

  • A domain or subdomain you control, with DNS pointing at this server.
  • A and optional AAAA records (see below).
  • TCP ports 80 (HTTP) and 443 (HTTPS) reachable from the public internet through Ubuntu UFW and, if applicable, your hosting or cloud provider's external firewall (firewall rules or security groups).

DNS and ports required for automatic HTTPS

  • A record — maps the hostname to the server's IPv4 address.
  • AAAA record — maps the hostname to IPv6; add one only when this server is reachable over IPv6.

For automatic HTTPS in this guide:

  • The hostname in the Caddyfile must resolve to this server.
  • TCP 80 (HTTP) and 443 (HTTPS) must be reachable from the internet.
┌─────────────────┐
│   example.com   │
└────────┬────────┘
         ↓
┌─────────────────┐
│   DNS A record  │
└────────┬────────┘
         ↓
┌─────────────────┐
│  203.0.113.10   │
└────────┬────────┘
         ↓
┌─────────────────┐
│  Ubuntu server  │
└────────┬────────┘
         ↓
┌─────────────────┐
│      Caddy      │
└─────────────────┘

If you publish an AAAA record, it must reach this same Caddy instance over IPv6. AAAA pointing elsewhere while clients prefer IPv6 can break certificate validation or connections.


Install Caddy on Ubuntu from the official repository

Add the Caddy repository

Refresh Ubuntu's package index before adding repository helpers (especially on a freshly installed server):

bash
sudo apt update

Install the packages Caddy’s install docs use for signed HTTPS repositories and curl:

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

Import the stable signing key:

bash
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg

Register the APT source list:

bash
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list

Make the key and list readable to unprivileged apt (matches upstream docs):

bash
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg

Repeat for the source list file:

bash
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list

Install Caddy

After the Caddy repository is added, run apt update again so APT loads the new source, then install:

bash
sudo apt update

Install the caddy package from the new repository:

bash
sudo apt install --yes caddy

The package enables caddy.service and drops a starter Caddyfile under /etc/caddy/.

Verify the installed version

Check the binary reports the Cloudsmith build:

bash
caddy version
output
v2.11.7 h1:yj0Y4fYZGPkSvibBJ1sTWE33xC0fxztVyXEW5iIdUT4=

Verify the Caddy systemd service

Ubuntu runs Caddy in the background as a systemd service named caddy.service. The service starts the web server at boot and lets you control it with systemctl.

Confirm the unit is enabled (configured to start at boot):

bash
systemctl is-enabled caddy
output
enabled

Enabled means Caddy is set to start at boot. Check whether it is active (running now):

bash
systemctl is-active caddy
output
active

For the full unit picture, use systemctl status:

bash
sudo systemctl status caddy --no-pager
output
● 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

Fix port 80 or 443 already in use

When another web server owns the ports, Caddy may exit with address already in use. A process listening on a port has already claimed that network port; only one normal web-server listener can usually own the same IP and port at a time.

Check listeners on 80 and 443:

bash
sudo ss -ltnp | grep -E ':(80|443)\b'
┌──────────────────────────────┐
│ nginx / Apache / other Caddy │
└──────────────┬───────────────┘
               ↓
┌──────────────────────────────┐
│      already owns port       │
└──────────────┬───────────────┘
               ↓
┌──────────────────────────────┐
│     Caddy cannot bind        │
└──────────────────────────────┘

If nginx is already listening on port 80, Caddy cannot use the same address and port. Stop the conflicting service only if Caddy is replacing it permanently:

bash
sudo systemctl stop nginx

Disable it at boot only when you do not plan to run nginx on this machine again:

bash
sudo systemctl disable nginx

Then start Caddy:

bash
sudo systemctl start caddy

Port and bind issues on other stacks are covered in Cannot assign requested address.


Understand the default Caddyfile

The Caddyfile is Caddy's human-readable configuration file. On this Ubuntu package, the main file is /etc/caddy/Caddyfile.

The package ships a default :80 site and the welcome page under /usr/share/caddy:

caddyfile
:80 {
	root * /usr/share/caddy
	file_server
}
  • :80 — Caddy accepts HTTP on port 80 for any hostname reaching this server; no public hostname, so no normal public automatic HTTPS.
  • root — directory where site files live.
  • file_server — serves files such as index.html from that directory.

The packaged service runs as the unprivileged caddy user and stores runtime data under /var/lib/caddy.

With Caddy listening on port 80, HTTP should return the bundled Caddy works! page. 127.0.0.1 is the server's loopback address, so this first request checks Caddy locally on the Ubuntu machine itself:

bash
curl -sI http://127.0.0.1/ | head -4
output
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 18753
Content-Type: text/html; charset=utf-8

To test from another machine, use the server's reachable public IP address or hostname instead of 127.0.0.1:

bash
curl -sI http://203.0.113.10/ | head -4
output
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Type: text/html; charset=utf-8
Server: Caddy

Serve a static website with Caddy

Create a web root

A web root is the directory whose files Caddy serves to visitors.

  • Caddy only needs read access to static files.
  • Prefer the root user or your deployment user as owner, with world read and directory traverse (a+rX).

Do not put secrets in the web root:

  • Application secrets, private keys, .env files, or backups
bash
sudo mkdir -p /var/www/caddy-demo

Add a minimal index.html for the demo:

bash
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

Leave ownership with root and grant read access to the caddy process:

bash
sudo chown -R root:root /var/www/caddy-demo

Directories need execute permission for traversal:

bash
sudo chmod -R a+rX /var/www/caddy-demo
┌─────────────────────────┐
│ deployment / root user  │
└────────────┬────────────┘
             ↓
┌─────────────────────────┐
│      website files      │
└────────────┬────────────┘
             ↓
┌─────────────────────────┐
│          Caddy          │
└─────────────────────────┘

Edit the Caddyfile

Back up the package default before replacing it:

bash
sudo cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.bak

Open the file in Nano:

bash
sudo nano /etc/caddy/Caddyfile

Replace the contents with this demo site (modern Caddy accepts root without a * matcher):

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

root tells Caddy where the website files are stored. file_server tells Caddy to serve files such as index.html from that directory.

In Nano, press Ctrl+O, then Enter to save, and Ctrl+X to exit.

Validate and reload the Caddyfile

caddy validate checks the configuration before it is applied; systemctl reload applies a valid config without intentionally stopping the running service.

First, format the Caddyfile:

bash
sudo caddy fmt --overwrite /etc/caddy/Caddyfile

Validate the syntax:

bash
sudo caddy validate --config /etc/caddy/Caddyfile
output
Valid configuration

Reload Caddy to apply the validated configuration:

bash
sudo systemctl reload caddy

Confirm the demo HTML is served:

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

In a browser, open http://<your-server-public-ipv4>/ (or your hostname once DNS points at the server). HTTP shows Not secure until you switch the Caddyfile to your domain and Caddy obtains a certificate:

Browser showing the Caddy demo static site served over HTTP from a public IPv4 address


Enable automatic HTTPS for a public domain

Before you continue

You do not need a domain for installation, local testing, or HTTP testing by IP. A real domain is required only for this public automatic-HTTPS section.

You need:

  • A domain or subdomain you control
  • A public IPv4 address for the Ubuntu server (and IPv6 if you publish AAAA records)
  • DNS pointing the hostname at that address
  • TCP ports 80 and 443 reachable from the internet through UFW and any external firewall used by your hosting or cloud provider

example.com is only a placeholder. For your site:

  • Replace it with a hostname you control (for example app.yourdomain.com).
  • Create a DNS A record pointing that name to your server's public IPv4 address.
  • Add an AAAA record as well if you use IPv6.

With a public hostname in the Caddyfile (instead of :80), Caddy obtains and renews a publicly trusted TLS certificate through ACME—normally without Certbot or a separate renewal cron.

Point DNS to the Caddy server

Example record at your DNS provider (field names differ by vendor):

DNS field Example
Type A
Name / Host caddy
Value / Points to 203.0.113.10
Resulting hostname caddy.yourdomain.com

DNS provider interfaces use different labels such as Name, Host, or Record, but the goal is the same: the hostname must resolve to your server's public IP.

Confirm the name resolves to this server before you change the Caddyfile (replace example.com with the hostname you configured):

bash
dig +short A example.com
output
203.0.113.10

Check the dig result:

  • It should match your Ubuntu server's public IPv4 address.
  • If not, fix the A record at your DNS provider and wait for propagation (TTL sets how long old answers may linger).
  • If DNS goes through a CDN or proxy, validation and firewall rules can differ; for this guide, point the hostname directly at the Caddy server while you complete the first HTTPS setup.

Update the Caddyfile

Replace the :80 block with a hostname block once DNS points at this server:

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

Replace example.com with the exact hostname you configured in DNS. For example, if you created an A record for caddy.mydomain.com, use:

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

First, format the Caddyfile:

bash
sudo caddy fmt --overwrite /etc/caddy/Caddyfile

Validate the syntax:

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

Reload Caddy to apply the validated configuration:

bash
sudo systemctl reload caddy

After reload, Caddy begins obtaining the certificate automatically. Follow the service logs to watch the process:

bash
sudo journalctl -u caddy -f

Verify HTTPS

From a client that resolves your domain to this server, request HTTPS (replace example.com with your hostname):

bash
curl -sI https://example.com/ | head -6
output
HTTP/2 200
accept-ranges: bytes
content-type: text/html; charset=utf-8
server: Caddy

From curl, expect:

  • HTTP/2 or HTTP/1.1 200
  • server: Caddy
  • With Caddy's default automatic HTTPS behavior, HTTP requests to that hostname are redirected to HTTPS

In a browser on your hostname:

  • The address bar should show HTTPS and your browser's secure-connection indicator (padlock or equivalent).
  • That indicator reflects the certificate Caddy obtained—not text on the page.

The figure uses caddy.mydomain.com as a placeholder URL; substitute the hostname you configured in DNS and the Caddyfile:

Browser showing the Caddy demo site over HTTPS with a secure connection indicator and a placeholder hostname in the address bar


Optional: Configure Caddy as a reverse proxy

A reverse proxy accepts the browser request through Caddy and forwards it to an application already running on another local port, such as 8080.

To front an application on another port, keep the domain block and proxy to the upstream:

caddyfile
example.com {
	reverse_proxy localhost:8080
}

Run sudo caddy fmt, sudo caddy validate, and sudo systemctl reload caddy the same way as for a static site.


Optional: Use HTTPS on localhost

For local development on a desktop or lab VM, use localhost as the site address:

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

After validate and reload, test https://localhost/ (not 127.0.0.1 unless you add that name to the Caddyfile). If your shell sets HTTPS_PROXY, bypass it for loopback:

bash
curl --noproxy '*' -sI https://localhost/

Before the local CA is trusted, curl may fail with a certificate error (exit code 60 on many systems).

Trust Caddy's local CA

For local HTTPS, Caddy creates its own certificate authority (CA). Your computer must trust that CA before browsers and tools accept Caddy's locally issued certificates without warnings.

The packaged service runs as the unprivileged caddy user, so install the local root into the system trust store with sudo:

bash
sudo caddy trust
output
{"level":"info","msg":"certificate installed properly in linux trusts"}

Then HTTPS to localhost should succeed without -k:

bash
curl --noproxy '*' -sI https://localhost/ | head -4
output
HTTP/2 200
content-type: text/html; charset=utf-8
server: Caddy

Some browsers keep a separate trust store; you may still need to import or trust the local CA in the browser even after sudo caddy trust.


Configure UFW for Caddy

When UFW is active, allow HTTP and HTTPS:

bash
sudo ufw allow 80/tcp

Allow HTTPS on TCP as well:

bash
sudo ufw allow 443/tcp

Keep SSH (port 22) allowed before sudo ufw enable on remote servers. UFW applies new allow rules immediately; a separate ufw reload is not required.

Optional: Allow HTTP/3

Normal HTTP and HTTPS use TCP on ports 80 and 443. HTTP/3 additionally uses UDP port 443. HTTPS over TCP still works if you skip this rule:

bash
sudo ufw allow 443/udp

Update Caddy on Ubuntu

Because Caddy is an APT package from Cloudsmith, refresh indexes and upgrade only the caddy package:

bash
sudo apt update

Upgrade only the Caddy package:

bash
sudo apt install --only-upgrade caddy

Confirm the version and that the service stayed up:

bash
caddy version

The unit should still report active:

bash
systemctl is-active caddy

Uninstall Caddy

Stop the service and remove the package:

bash
sudo systemctl stop caddy

Remove the package and its maintainer scripts:

bash
sudo apt purge --yes caddy

Remove the Cloudsmith list and key if you no longer want the repository:

bash
sudo rm -f /etc/apt/sources.list.d/caddy-stable.list

Delete the signing keyring file as well:

bash
sudo rm -f /usr/share/keyrings/caddy-stable-archive-keyring.gpg

Leftover paths after purge:

  • /etc/caddy — configuration
  • /var/lib/caddy — certificates and PKI state (delete only if you want that material gone)

See how to remove software on Ubuntu for purge vs remove.


Troubleshooting

Symptom Likely cause Fix
address already in use on :80 or :443 nginx, Apache, or another listener sudo ss -ltnp with `grep -E ':(80
curl shows wrong Server header Traffic hits another web server Free ports 80/443; confirm systemctl is-active caddy is active
HTTPS fails for public domain DNS or firewall Fix A/AAAA; allow 80 and 443 on UFW and the provider's external firewall
Valid configuration but site unchanged Config not reloaded sudo systemctl reload caddy after edits
Permission denied reading site files caddy cannot read the path sudo chmod -R a+rX /var/www/yoursite and traverse permission on parent directories
curl to localhost returns proxy errors HTTPS_PROXY set curl --noproxy '*' for loopback tests
Certificate errors on localhost Local CA not trusted sudo caddy trust; trust the CA in the browser if needed

Follow logs after changes:

bash
sudo journalctl -u caddy -f

Summary

Ubuntu 24.04 and 26.04 include an older Caddy 2.6.2 build in Universe (package revisions differ by release), but the official Caddy APT repository installs the current stable release (2.11.7 at test time) with the same curl, gpg, and apt install caddy flow on both releases. The package enables caddy.service, serves the default welcome page from /usr/share/caddy, and keeps the main config in /etc/caddy/Caddyfile.

For your own site:

  • Web root: root:root with a+rX; root /path in the Caddyfile
  • After edits: caddy fmt, caddy validate, systemctl reload caddy
  • Public domain name in the site address for ACME HTTPS when DNS and ports are correct
  • localhost plus sudo caddy trust for local TLS

If install fails immediately, check whether port 80 or 443 is already taken—the usual blocker when nginx or Apache was installed first.


References


Frequently Asked Questions

Is Caddy available in Ubuntu's default repository?

Ubuntu provides caddy in Universe on both releases: 2.6.2-14 on 26.04 and 2.6.2-6ubuntu0.24.04.3 on current 24.04 amd64 updates. That is not the same as the current upstream stable series from the official Caddy APT repository.

Should I use Ubuntu's Caddy package or the official repository?

Use Ubuntu's package if 2.6.2 meets your needs and you want Ubuntu-only lifecycle management. Use the official Caddy repository when you want the current stable Caddy and normal apt upgrades, which is what this guide installs.

Does Caddy need Certbot?

No for typical automatic HTTPS. When the Caddyfile site address is a public hostname with correct DNS, Caddy obtains and renews certificates through ACME. Certbot is for stacks that do not manage TLS inside the web server.

Where is the Caddyfile on Ubuntu?

/etc/caddy/Caddyfile. After edits, run sudo caddy fmt --overwrite /etc/caddy/Caddyfile, sudo caddy validate --config /etc/caddy/Caddyfile, then sudo systemctl reload caddy.

Does Caddy start automatically after installation?

Yes. The Debian package enables and starts caddy.service. If the unit fails with address already in use, another process is holding port 80 or 443.

Does this guide work on Ubuntu 24.04?

Yes. The official Cloudsmith stable repository and install commands are the same on Ubuntu 24.04 and 26.04; only the Ubuntu Universe package revision differs (2.6.2-14 vs 2.6.2-6ubuntu0.24.04.3).

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Omer Cakmak

Linux Administrator

Highly skilled at managing Debian, Ubuntu, CentOS, Oracle Linux, and Red Hat servers. Proficient in bash scripting, Ansible, and AWX central server management, he handles server operations on OpenStack, KVM, Proxmox, and VMware.

  • Debian
  • Ubuntu
  • Linux
  • Red Hat Enterprise Linux
  • Shell Script
  • System Administration