Web hostingWritten by GLWS14 min readUbuntu 24.04 LTS / AlmaLinux 9
Commands run on your server unless a section says otherwise. Replace example usernames, addresses and paths with your own values. Keep a working session open when changing remote access.

Why Caddy

Caddy is a web server and reverse proxy that obtains and renews HTTPS certificates by itself. The configuration for a whole site can be two lines, which makes it a good fit for a server that hosts several small applications, each on its own domain. It is an alternative to the Nginx and Certbot setup, not an addition: use one of them on ports 80 and 443.

Before you start, create a DNS A record for each domain pointing to the server (see the domain guide). Each application should listen only on a local port, such as 127.0.0.1:3000 or 127.0.0.1:8080.

Both systems: check that ports 80 and 443 are free
sudo ss -lntp | grep -E ":(80|443) " || echo "Ports 80 and 443 are free"

Install Caddy on Ubuntu

Add Caddy's official package repository and install the package. It installs a systemd service, a caddy user and a default configuration file.

Ubuntu only
sudo apt install debian-keyring debian-archive-keyring apt-transport-https curl
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
caddy version

Install Caddy on AlmaLinux

On RHEL-compatible systems the project provides packages through a COPR repository. Enable it and install. Check Caddy's installation page if the repository name changes.

AlmaLinux only
sudo dnf install 'dnf-command(copr)'
sudo dnf copr enable @caddy/caddy
sudo dnf install caddy
caddy version

Open the firewall

Allow HTTP and HTTPS. Caddy also supports HTTP/3, which uses UDP port 443, so open that too. Certificates are obtained through ports 80 and 443, so a closed port stops them.

Ubuntu
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
AlmaLinux
sudo firewall-cmd --permanent --add-service=http --add-service=https --add-port=443/udp
sudo firewall-cmd --reload
sudo setsebool -P httpd_can_network_connect 1

Write the Caddyfile

The Caddyfile lists one block per domain. The reverse_proxy line forwards requests to a local port, and certificates are handled automatically for every domain you list. This example hosts two applications, redirects www to the main name and serves a static site from a folder. Replace the domains and ports.

The static site uses its own directory. Do not point file_server at a PHP application directory: it serves files without executing PHP and could expose configuration secrets.

Both systems: replace /etc/caddy/Caddyfile
sudo install -d -m 755 /var/www/caddy-site
printf '<!doctype html><title>My Caddy site</title><h1>My site is online.</h1>\n' | sudo tee /var/www/caddy-site/index.html >/dev/null
sudo chmod 644 /var/www/caddy-site/index.html
sudo tee /etc/caddy/Caddyfile >/dev/null <<'EOF'
{
    email [email protected]
}

app.example.com {
    reverse_proxy 127.0.0.1:3000
}

notes.example.com {
    reverse_proxy 127.0.0.1:8080
}

www.example.com {
    redir https://example.com{uri} permanent
}

example.com {
    root * /var/www/caddy-site
    file_server
    encode gzip
}
EOF
sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
DNS points each domain to the VPS. Caddy or Nginx handles public HTTPS and forwards requests to the matching application on a private loopback port.
DNS points each domain to the VPS. Caddy or Nginx handles public HTTPS and forwards requests to the matching application on a private loopback port.

Reload and watch the certificates being issued

Reload the service. On first use Caddy contacts the certificate authority for each domain, so the first request can take a few seconds. If a domain does not point to the server yet, Caddy keeps retrying and logs the reason. Watch the journal while it works.

Both systems
sudo systemctl enable --now caddy
sudo systemctl reload caddy
sudo journalctl -u caddy -f --since "1 minute ago"

Test each site

Request each domain over HTTPS, and confirm the redirect from HTTP and from www. Check the certificate dates, and test that each application answers on its own domain and not on another one.

Both systems
curl -I https://app.example.com/
curl -I http://app.example.com/
curl -I https://www.example.com/
echo | openssl s_client -connect app.example.com:443 -servername app.example.com 2>/dev/null | openssl x509 -noout -subject -dates
RECORDED LAB OUTPUT · Ubuntu 24.04 LTS
Valid configuration
Caddy validated static host
Hello from my app
Hello from the backend
HTTP/2 301 
location: https://example.com/test
HTTP/1.1 308 Permanent Redirect
Recorded static host, two private backends and redirect checks using trusted local test certificates. Public automatic certificate issuance needs working DNS and inbound reachability.

Add another application

To add a site, append a block to the Caddyfile, validate and reload. Keep each backend bound to 127.0.0.1 so that the only public entry point is Caddy. Add basic authentication to a block for an administration tool you want to protect with a password, using caddy hash-password to create the hash.

Both systems
caddy hash-password
# then in a site block:
# basic_auth {
#     admin PASTE_THE_HASH
# }
sudo caddy validate --config /etc/caddy/Caddyfile && sudo systemctl reload caddy

Troubleshoot

If a certificate is not issued, the usual causes are a DNS record that does not point to the server, ports 80 or 443 blocked, a stale AAAA record, or another program using the ports. The journal names the challenge and the error. If a site returns 502, the application behind it is not listening, so test it directly with curl against its local port.

Both systems
sudo journalctl -u caddy --no-pager | grep -iE "error|tls|acme" | tail -n 15
sudo ss -lntp | grep -E ":(80|443|3000|8080) "
dig +short A app.example.com; dig +short AAAA app.example.com

Official documentation

These upstream references provide more detail on the commands and workflows in this guide.