Running self-hosted services on a home server or VPS usually means juggling port numbers, remembering IP addresses, and manually managing TLS certificates. Caddy changes that by acting as a reverse proxy that handles HTTPS automatically, routes traffic to the right service by domain name, and requires almost no configuration overhead to get right.

What Caddy Actually Does and Why It Fits Self-Hosting
A reverse proxy sits in front of your services and forwards incoming requests to the appropriate backend based on rules you define. Instead of exposing Jellyfin on port 8096, Gitea on port 3000, and Grafana on port 3001, you expose only ports 80 and 443 through Caddy, and each service gets its own subdomain. Traffic hits Caddy first, which decides where it goes.
What separates Caddy from Nginx or Apache in a self-hosting context is its built-in ACME client. Caddy talks to Let’s Encrypt or any ACME-compatible certificate authority on its own, issues certificates, and renews them before they expire. You never write a Certbot cron job, never manually reload a config after renewal, and never wake up to an expired cert on a service you forgot about. The entire TLS lifecycle is managed automatically once you point a domain at your server.
Caddy is configured through a single file called the Caddyfile. Its syntax is designed to be readable without referencing documentation every time you need to add a site block. A basic reverse proxy directive fits on one line. This matters when you are managing a dozen self-hosted services, because the complexity stays manageable even as your stack grows.
For the setup covered here, the assumption is a Linux server (Ubuntu or Debian), a domain name with DNS you control, and one or more backend services already running locally on non-standard ports. If you are running something like Immich as a self-hosted Google Photos alternative, Caddy is the natural next step for putting it behind a proper HTTPS subdomain instead of an IP and port.
Installing and Configuring Caddy as a Reverse Proxy
Caddy is not always in the default package repositories at the latest version, so the recommended installation method is through Caddy’s own apt repository. Start by adding the Caddy GPG key and repository, then install the package directly.
sudo apt install -y 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
Once installed, Caddy runs as a systemd service and the default Caddyfile lives at /etc/caddy/Caddyfile. Open that file and replace the default placeholder content with your actual site blocks. Each block follows the same pattern: domain name as the block label, and reverse_proxy pointing at the local address where your service listens.
photos.yourdomain.com {
reverse_proxy localhost:2283
}
finance.yourdomain.com {
reverse_proxy localhost:8080
}
git.yourdomain.com {
reverse_proxy localhost:3000
}

That is a complete, working Caddyfile for three services. Caddy will obtain TLS certificates for each domain automatically the first time a request comes in, provided your DNS A records point to your server’s public IP and ports 80 and 443 are open in your firewall. If you are behind a NAT or using a cloud VPS, confirm that those ports are forwarded or open in the security group before reloading Caddy.
Apply the new configuration with sudo systemctl reload caddy. Check the status with sudo systemctl status caddy and watch the logs with sudo journalctl -u caddy -f to confirm certificate issuance is working. If certificate requests are failing, the most common causes are DNS not yet propagated, a firewall blocking port 80 (which ACME HTTP challenge requires), or the domain not resolving to the correct IP.
Caddy also supports a few useful directives beyond basic proxying. Adding encode gzip inside a site block enables response compression. The header directive lets you inject or strip HTTP headers, which is useful for services that need a real IP forwarded or for adding security headers like Strict-Transport-Security. For services that use WebSockets, Caddy handles the protocol upgrade automatically with no additional configuration, which is more than can be said for a basic Nginx setup.
Advanced Options: Wildcards, Internal Services, and Docker

If you are running many subdomains under a single domain, wildcard certificates reduce the number of individual ACME requests and simplify your Caddyfile. Wildcard cert issuance requires DNS challenge instead of HTTP challenge, which means you need a DNS provider with an API that Caddy can call. Caddy’s module system supports this through DNS provider plugins – you compile or download a Caddy build that includes the relevant plugin (Cloudflare, Route53, and others are available), then add your API credentials to the Caddyfile’s global options block. The DNS challenge also works for services that are never exposed to the public internet, making it the right approach for internal-only dashboards or admin panels you want HTTPS on without public accessibility.
For Docker-based setups, the cleanest pattern is to run Caddy as a container alongside your services and mount the Caddyfile in as a volume. Caddy can proxy to containers by service name when both are on the same Docker network, so your reverse_proxy directive points at appname:port instead of localhost:port. Some self-hosters use Caddy’s JSON API or tools like caddy-docker-proxy, which reads Docker labels and generates Caddy configuration on the fly – so adding a new container with the right labels automatically routes it through Caddy without editing any config file manually. That approach works well when the stack grows fast, though it adds complexity that a straightforward Caddyfile does not have. The decision mostly comes down to how often you are spinning up and tearing down services versus running a stable set of long-lived applications.
Frequently Asked Questions
Does Caddy automatically renew TLS certificates?
Yes. Caddy manages the full certificate lifecycle through its built-in ACME client, issuing and renewing certificates from Let’s Encrypt without any manual intervention.
Can Caddy proxy services running inside Docker containers?
Yes. When Caddy and your services share a Docker network, you can proxy by container name and port directly in the Caddyfile, or use a tool like caddy-docker-proxy to automate routing via Docker labels.
What ports need to be open for Caddy to work?
Ports 80 and 443 must be open and accessible from the internet. Port 80 is required for the HTTP-01 ACME challenge used to issue TLS certificates, unless you switch to DNS challenge mode.





