How to Use Caddy: The Beginner's Guide (First Caddyfile, Static Site, Reverse Proxy, and the HTTPS That Configures Itself)
CodexFuse Team · 2026-10-31T03:57:27.257Z · 15 min read
How to Use Caddy: The Beginner's Guide (First Caddyfile, Static Site, Reverse Proxy, and the HTTPS That Configures Itself)
Learning how to use Caddy is the moment a self-hosted developer stops fighting certificates. Caddy is an open-source web server written in Go that was the first to serve every site over HTTPS automatically and by default — there is no certbot step, no renewal cron job, no TLS block to get wrong. The official docs at caddyserver.com put it plainly: by default, Caddy serves all sites over HTTPS, provisions certificates for all your sites, keeps them renewed, and redirects HTTP to HTTPS, with no separate tooling required. This caddy for beginners tutorial runs the whole ladder on the current stack: install, your first Caddyfile, a static site, a reverse proxy in front of a real app, the DNS-and-ports prerequisite that automatic HTTPS depends on, the local-development branch, and the Docker branch — ending with the three-line security rule for the admin API that every beginner should learn on day one.
You will build in this order: point a domain at your server, install Caddy from the official repository, write a four-line Caddyfile, serve a static site, reverse-proxy an app running on a local port, watch the first certificate provision itself in the logs, and verify the result. Once HTTPS is serving, you can confirm the certificate chain from the outside with a free SSL certificate checker — paste your domain and it reports the certificate's issuer, validity dates, and chain the same way a visitor's browser sees them, which is the fastest sanity check that the automation worked.
What Is the Fastest Way to Get a Site on HTTPS with Caddy?
Point your domain's A record at the server, install Caddy, write four lines of Caddyfile, and reload — HTTPS configures itself in about five minutes.
- Point your domain at the server: create an A record for
example.comthat resolves to your server's public IP, and confirm ports 80 and 443 are open and reachable. - Install Caddy from the official package repository (commands below) and verify with
caddy version. - Write your first Caddyfile — the whole site is four lines:
example.com { root * /var/www/example.com file_server }. - Check the syntax with
caddy fmt --overwrite /etc/caddy/Caddyfileandcaddy validate, then apply it withsudo systemctl reload caddy. - Watch the provisioning:
sudo journalctl -u caddy -fshows Caddy reaching out to Let's Encrypt and obtaining the certificate, then openhttps://example.com— it just works.
What Is Caddy, Exactly?
Caddy is a web server the way a modern beginner would design one: one flat config file, sane defaults, and certificates as a built-in feature rather than a weekend project. The project calls itself a powerful, enterprise-ready, open-source web server with automatic HTTPS, written in Go. Its configuration format is the Caddyfile — site addresses plus short directives like root, file_server, and reverse_proxy — and the config adapts to Caddy's native JSON behind the scenes, which is also fully editable through a REST admin API.
The feature that defines it is automatic HTTPS. Where Nginx asks you to install certbot, run it once per domain, and maintain a renewal timer, Caddy treats HTTPS as the default state: put a domain name in the config and it obtains the certificate from a public authority, installs it, redirects plain HTTP to HTTPS, and renews it forever. Its default certificate authorities are Let's Encrypt and then ZeroSSL as a fallback, and the docs are explicit that this needs no downtime, no extra configuration, and no separate tooling.
That is also the clean way to draw the Caddy vs nginx line. Nginx gives you built-in response caching, rate and connection limits, and a proxy your whole team already knows — it wins where you need fine control at the edge. Caddy wins where certificates are the main chore: small teams, many small sites, homelabs, and developers who want the server to get out of the way. They are not rivals in the same lane; they are answers to different pains.
What Do You Need Before the First Caddyfile?
Automatic HTTPS needs three things from you before it can do its magic: a real public domain, open ports, and DNS that actually points at the server. The official automatic-HTTPS page lists the conditions precisely: your domain's A/AAAA records point to your server, ports 80 and 443 are open externally, Caddy can bind to those ports (or they are forwarded to it), your data directory is writable and persistent, and the domain name appears in the config. Miss any one of these and the certificate step fails — which is why this section comes before installation, never after.
Start with the DNS record. In your registrar or DNS provider, create an A record for the exact hostname you will serve — example.com or a subdomain like app.example.com — pointing at your server's public IPv4 address (add an AAAA record too if you have IPv6). If your DNS lives behind a managed panel, the Cloudflare beginner guide walks through record setup end to end; one caution from the field is that a proxied orange-cloud record must keep SSL on Full (strict) so the edge-to-origin connection stays encrypted rather than silently downgraded.
Then the ports and the box. Ports 80 and 443 must be reachable from the public internet — open them in your VPS provider's firewall as well as in the OS firewall, because the ACME challenges that issue the certificate arrive over those ports. And you need a server to put Caddy on in the first place; if that part is still new, the what is VPS hosting guide explains the shape of a virtual private server and what you are renting when you buy one.
How Do You Install Caddy?
Use the official package for your distribution — the project explicitly recommends the official packages over static binaries for production systems. These are the current official routes, taken from caddyserver.com's install page at write time (repository lines drift over the years, so always re-check the official page rather than copying an apt block from an old blog post).
On Debian, Ubuntu, or Raspbian, add the official Cloudsmith repository and install:
sudo apt install --yes 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 chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
Installing this package automatically starts and runs Caddy as a systemd service named caddy, so there is no separate daemon step. On Fedora or RHEL-based systems, the official route is the COPR repository:
dnf install dnf5-plugins
dnf copr enable @caddy/caddy
dnf install caddy
On Arch Linux and derivatives it is one command — pacman -Syu caddy — and the Docker route is docker pull caddy. Mac and Windows have community-maintained routes (brew install caddy, choco install caddy), fine for a laptop but not the path this guide follows. Verify the install with caddy version — the current Docker Hub tags at write time are at 2.11.7, which gives you a sense of the present release line.
How Do You Write Your First Caddyfile?
A Caddyfile is read in one glance: the first line names the site, and the indented lines are directives that say what to do with requests. Here is a complete, working first Caddyfile for a domain you own:
example.com {
root * /var/www/example.com
file_server
}
That is the whole thing — three moving parts. example.com is the site address: it tells Caddy which hostname this block answers for, and it is also the trigger that activates automatic HTTPS. root * /var/www/example.com sets the directory that files are served from, and file_server turns on the static file server. One file-serving fact beginners miss: the caddy system user must be able to read that directory, so set ownership or permissions accordingly or you will get a permission error that looks like a Caddy bug.
Before any Caddyfile goes live, run the two safety commands. caddy fmt --overwrite /etc/caddy/Caddyfile normalizes the formatting so every tutorial you read looks the same as yours, and caddy validate tests whether the config is valid without starting anything. Then apply it with zero downtime: sudo systemctl reload caddy hands the running process the new config without dropping connections — restarts are for kernel upgrades, not for config edits.
How Do You Serve a Static Site with Caddy?
Put your files in a directory, aim the two-line block at it, reload — and you have a production static site with HTTPS. The minimal static-site config from the official docs is exactly the block you already wrote:
example.com {
root * /var/www/example.com
file_server
}
Build the directory first: sudo mkdir -p /var/www/example.com, drop in an index.html, and make sure the caddy user can read it. After sudo systemctl reload caddy, opening https://example.com serves your files over HTTPS with an automatically provisioned certificate — there was never a certificate step. That is the piece that takes the longest to sink in for people coming from certbot: you did not skip the certificate, it simply happened while you were reading the last paragraph.
Two useful upgrades for a real static site. First, compression — add encode zstd gzip inside the block and responses get compressed on the fly. Second, the local-development variant: for a laptop project you do not need a domain at all. A Caddyfile with localhost:8080 { root * ./public file_server } serves your project folder over plain HTTP for local testing, and the one-liner caddy file-server --browse spins up a file server with directory listings in the current directory — a handy detail is that the one-shot file-server command disables the admin API, which makes it easier to run several instances on one machine.
How Do You Reverse-Proxy an App with Caddy?
One directive turns Caddy into the public front door for any app listening on a local port: reverse_proxy. If your Node, Python, or Go app is already running and answering on port 3000, this Caddyfile puts it on the public internet with automatic HTTPS:
example.com {
reverse_proxy 127.0.0.1:3000
}
The order of operations matters: verify the app is actually listening before you involve Caddy — curl http://127.0.0.1:3000 from the server terminal should return your app's response. If it does not, Caddy will faithfully proxy to a dead port and you will debug the proxy for an hour when the app was never up. Once the app answers locally, reload Caddy and the same URL that served static files now serves your app, certificate and all, with HTTP redirected to HTTPS.
Scaling the pattern is more of the same. Multiple services get their own site blocks or path prefixes in one Caddyfile:
example.com {
reverse_proxy /api/* localhost:8080
reverse_proxy /* localhost:3000
}
grafana.example.com {
reverse_proxy localhost:3001
}
Current 2026 reverse-proxy guides note two things Caddy handles without extra directives: it fills in the common forwarding headers (X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host) so your app sees the real client, and WebSockets pass through the proxy without additional configuration. Every config change still goes through the same safe ritual — caddy validate, then sudo systemctl reload caddy — because a reload is zero-downtime and a restart is unnecessary.
How Do You Watch Caddy Provision HTTPS?
You do not configure anything — you watch the logs and confirm from the outside. The first time Caddy loads a config containing a public domain, it reaches out to Let's Encrypt (falling back to ZeroSSL), completes an ACME challenge over port 80 or 443, stores the certificate in its data directory, and starts serving HTTPS. Tail the service log while you reload and you can see it happen:
sudo journalctl -u caddy -f
The log lines show Caddy obtaining the certificate for your domain, and from that point every request is served over HTTPS with the HTTP-to-HTTPS redirect applied automatically. Verify the way a visitor would: open https://example.com and check the padlock, or run curl -vI https://example.com and watch the 301 redirect from the port-80 URL to the HTTPS one.
Two edge cases belong in every beginner guide. First, testing: if you are experimenting with configs that reload often, point Caddy at the ACME staging endpoint (https://acme-staging-v02.api.letsencrypt.org/directory) instead of production — the official docs warn that repeated failed issuances against the production endpoint can hit rate limits that block your access to HTTPS for up to a week. Second, the no-ports case: if your server cannot expose ports 80 and 443, the DNS challenge issues certificates by setting a special TXT record through your DNS provider's API instead — no open ports required, but it needs DNS-provider credentials and a plugin build of Caddy. Wildcard certificates also go through the DNS challenge, since the other challenge types cannot prove control of a wildcard.
How Do You Run Caddy in Docker?
Use the official caddy image with a Compose file, mounting your config directory and persisting the data directory — certificates live there and must not be treated as a cache. This is the official Docker Hub sample, the exact pattern the image maintainers document:
services:
caddy:
image: caddy:2.11.7
restart: unless-stopped
cap_add:
- NET_ADMIN
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- $PWD/conf:/etc/caddy
- $PWD/site:/srv
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
Put your custom Caddyfile at $PWD/conf/Caddyfile and your static files at $PWD/site. Two details from the official notes that save beginners real pain: mount the conf directory, never the Caddyfile file directly — some editors replace the file's inode on save, and a direct file mount then keeps serving the stale copy after reloads. And pin a version tag like 2.11.7 rather than :latest in anything that matters; the Docker Hub page says so outright for production.
Reloads inside Docker follow the same zero-downtime ritual through the container: docker compose exec -w /etc/caddy caddy caddy reload. If the container side of Docker is new to you, the Docker beginner guide covers images, volumes, and Compose from the ground up — the Caddy service above will make much more sense after it. One networking gotcha to internalize early: inside a container, 127.0.0.1 means the Caddy container itself, not your host or your app container — proxy to sibling containers by service name on a shared Docker network instead.
How Do You Develop Locally with Caddy?
Use localhost or an http:// prefix and Caddy skips the public certificate dance entirely. Local hosts — localhost, .local, .internal, .home.arpa, and bare IP addresses — do not qualify for publicly-trusted certificates, so Caddy serves them over HTTPS using its own locally-generated certificate authority instead. The first time it does this it tries to install its root certificate into your system's trust store (as "Caddy Local Authority"), which may prompt for your password once; after that, your browser trusts local Caddy sites with no warnings.
The trust is strictly local: any machine that has not installed that root CA will show security errors for these sites, which is exactly what you want from a development certificate. For quick experiments the one-shot commands need no Caddyfile at all — caddy file-server --root ./public --listen :8080 serves a folder, and caddy file-server --domain example.com will even attempt real HTTPS when the DNS points at the machine. Prefixing a site address with http:// in a Caddyfile explicitly disables automatic HTTPS for that site, which is the clean way to say "plain HTTP on purpose" for local testing.
Caddy vs Nginx: Which Should You Pick?
Pick Caddy when certificates are the chore and pick Nginx when edge control is the job. A good rule from the current 2026 comparisons: Caddy for small teams and many small sites where HTTPS automation is the main win; Nginx where you need built-in response caching, rate and connection limits, or a proxy your team already knows well. Both handle WebSockets and high traffic without trouble on a small VPS.
| Caddy | Nginx | |
|---|---|---|
| HTTPS certificates | Automatic by default — obtained, installed, renewed | Manual — certbot plus a renewal timer you maintain |
| Config style | Short Caddyfile; site address plus directives | Verbose config blocks; precise but long |
| Reverse proxy setup | reverse_proxy 127.0.0.1:3000 — one line | proxy_pass plus headers, SSL blocks, cert paths |
| Built-in caching | Not the focus | Built-in response caching |
| Config reloads | Zero-downtime via caddy reload or admin API | Reload via signal; also graceful |
| Best for | Beginners, homelabs, many small sites | Fine control, caching, teams with existing Nginx knowledge |
Traefik sits in a third lane worth one sentence: it is the container-native reverse proxy that discovers services from Docker labels, which is a different answer again — Caddy is the simple file, Traefik is the auto-discovering orchestrator, Nginx is the precise instrument.
What Are the Beginner Mistakes to Avoid?
Almost every first-week Caddy failure is one of these, and all of them are made before the first Caddyfile is written. The DNS record, the firewall, and the reload ritual are the whole checklist.
| Mistake | What happens | The fix |
|---|---|---|
| Writing the Caddyfile before DNS points at the server | The ACME challenge looks up your domain, finds nothing, and no certificate is issued | Create the A record first and confirm it resolves with dig before reloading |
| Ports 80 or 443 blocked by a firewall | Challenges never arrive; Caddy retries in the background while the site stays HTTP-only or broken | Open both ports in the VPS provider firewall and the OS firewall before the first reload |
| Testing against the production ACME endpoint repeatedly | Rate limits can block your domain's HTTPS for up to a week | Use the Let's Encrypt staging endpoint while experimenting |
| Editing the Caddyfile and forgetting to reload | Nothing changes and you debug a config that was never loaded | Always caddy validate then sudo systemctl reload caddy after every edit |
| Mounting the Caddyfile file directly in Docker | Editor inode swaps mean the container keeps the stale config after reloads | Mount the conf directory at /etc/caddy, never the single file |
Prefixing a site with http:// and expecting HTTPS | That prefix explicitly disables automatic HTTPS for the site | Use a bare domain for HTTPS; use http:// only when you mean plain HTTP |
| Exposing the admin API beyond localhost | Anyone who reaches it can replace your running configuration remotely | Keep it on localhost:2019; see the security section below |
How Do You Keep Caddy's Admin API Safe?
Leave it exactly where the defaults put it: localhost:2019. Caddy is configured through an administration endpoint — a REST API where a POST to /load replaces the entire running configuration with zero downtime, and the /config/... paths read and rewrite any part of the live config. That is a superpower on localhost and a loaded weapon on a public interface: anyone who can reach the endpoint can reconfigure your server.
The official API docs confirm the default address is localhost:2019 and note it can be changed with the CADDY_ADMIN environment variable or in the config itself. The rule is simple — never change it to a public address. If you run tooling that talks to the API from another machine, reach it through an SSH tunnel, not by binding it to the world. The convenience commands like caddy reload work through this same endpoint, which is why the docs note they require the admin endpoint to be enabled — disabling it entirely is possible but breaks the graceful reload workflow, so the correct posture is enabled and localhost-only.
Sources
Install routes, the Cloudsmith repository lines, and the systemd service behavior: caddyserver.com/docs/install (official docs, fetched 2026-10-31). Automatic HTTPS conditions, default certificate authorities, ACME challenges, local HTTPS, the staging endpoint, and rate-limit behavior: caddyserver.com/docs/automatic-https (official docs, fetched 2026-10-31). Admin API default address and the /load and /config endpoints: caddyserver.com/docs/api (official docs, fetched 2026-10-31). The caddy fmt, validate, reload, file-server, trust, and reverse-proxy commands: caddyserver.com/docs/command-line (official docs, fetched 2026-10-31). The official Docker Compose pattern, the /data and /config persistence rules, the do-not-mount-the-file note, and the current 2.11.7 tags: hub.docker.com/_/caddy (official Docker Hub image page, fetched 2026-10-31). Static-site and reverse-proxy Caddyfile patterns and the 2026 Caddy-vs-Nginx comparison: calmops.com/web/caddy-static-web-site-config/ (updated May 8, 2026), swetrix.com/blog/caddyfile-reverse-proxy (2026), domainindia.com reverse-proxy guide (2026), maketecheasier.com/install-caddy-webserver-linux/, dev.to homelab reverse-proxy walkthrough (2026).
Frequently Asked Questions
Do I need to buy a domain to learn Caddy?
No. The whole tutorial arc works on localhost, where Caddy serves HTTPS with its own local certificate authority after a one-time trust install. You only need a real public domain when you want a publicly-trusted certificate — that is the point where the DNS-and-ports prerequisite kicks in.
How long does the first certificate take?
Usually under a minute after the first reload, once DNS resolves and the ports are open. Certificate management runs in the background with retries, so Caddy will not block startup waiting for it — but if issuance fails, check the journal log first, because the answer is almost always DNS or a firewall.
Is Caddy free for commercial use?
Yes. Caddy is open-source software, free to download and run, including for commercial sites. There is no license fee for the server itself; the certificates come from free public authorities like Let's Encrypt, so the whole HTTPS stack costs nothing beyond your server.
Can Caddy replace certbot and my Nginx TLS config?
For the standard case, yes — that is its headline feature. The certificate obtains itself, installs itself, renews itself, and the HTTP-to-HTTPS redirect is automatic. You keep certbot around only if you have tooling or compliance processes built on it, or if you stay on Nginx for its caching and fine control.
Will Caddy renew my certificates automatically?
Yes, renewal is part of automatic HTTPS, not a separate setup step. Caddy tracks expiry and renews in the background; if an issuance or renewal hits an error, it retries with exponential backoff, switches challenge types and then certificate issuers, and keeps serving while it works the problem out.