Operations — 10 min read

Caddy and automatic HTTPS: the certificate you never have to think about again

What automatic HTTPS actually requires before it can work, why the Caddyfile is validated before every reload, the DNS and rate-limit facts that decide whether issuance succeeds, and the honest boundary of what Caddy does not do.

Published
September 28, 2026
Reading time
10 min read
Servor Team
Operations
Tags
Caddy · HTTPS
Lire cet article en français

The appeal is real, and it hides a checklist

Caddy's pitch is genuinely good: point a domain at the server, name it in a Caddyfile, and you get a valid TLS certificate that renews itself for as long as the server runs. No cron job to remember, no renewal you forgot until the site went red in a browser three months later. For most people, most of the time, it just works.

But “it just works” is a description of the happy path, not an explanation of it. Automatic HTTPS is automatic because Caddy performs an ACME challenge against Let's Encrypt on your behalf — and that challenge has preconditions. When it fails, it fails quietly enough that people blame Caddy for something DNS did. So this piece is about the part the one-liner skips: what has to be true before the certificate can be issued, and how to prove each of those things rather than hope for them.

Before the first package: read the machine and the ports

A certificate manager that wants ports 80 and 443 will not get far if something already owns them. Reconnaissance here is not a formality — it is the difference between a clean install and a service that refuses to start with a cryptic bind error.

  • Is Caddy already here? command -v caddy && caddy version. If it is, you are configuring an existing install, not laying down a new one — and you must not clobber a working Caddyfile.
  • Who owns 80 and 443? ss -tlnH 'sport = :80' 'sport = :443'. An nginx or Apache from an earlier experiment holding those ports is the single most common reason Caddy cannot bind. ACME's HTTP challenge needs port 80 reachable; the TLS-ALPN challenge needs 443. If another daemon holds them, decide which server terminates TLS before you go further.
  • Does the DNS already resolve to this machine? Automatic HTTPS proves you control the domain by being reached at it. If the A/AAAA record does not point here yet, issuance will fail no matter how correct the config is.

1. Install from the official repository, not a stray binary

On Debian and Ubuntu, Caddy ships from an official Cloudsmith repository: you import the signing key and add the source to /etc/apt/sources.list.d/caddy-stable.list before an ordinary apt-get install -y caddy. The reason to use the repository rather than dropping a downloaded binary in /usr/local/bin is unglamorous but decisive: the package brings a proper systemd unit, a service user, and the config path the rest of the world expects. A hand-placed binary works until you reboot and discover nothing starts it.

The proof this step worked is a version string from caddy version — not thatapt exited zero. One concrete fact, machine-checkable, that a human or a model can read without guessing.

2. The Caddyfile: where a domain becomes a certificate

The whole reason Caddy feels magical is the shape of its config. A working HTTPS site can be as short as a domain, a brace, and what to serve:

example.com { respond "OK" }

That block is the trigger for automatic HTTPS. Because you named a real domain — not :80, not localhost — Caddy knows it should obtain a certificate for it and serve over 443, redirecting plain HTTP up to it. Swap respond for a reverse_proxy 127.0.0.1:3000 and you have a TLS-terminating proxy in front of a loopback app, which is what most people actually want.

The rule that sits above the syntax: never overwrite an existing Caddyfile without backing it up first. A cp Caddyfile Caddyfile.bak costs nothing and is the difference between a mistake you undo in one command and an afternoon reconstructing routing from memory. And if no domain is available yet, serve on :80 without TLS deliberately, rather than naming a domain that does not resolve and watching issuance fail on a loop.

3. Validate before you reload — always

This is the habit that prevents most self-inflicted Caddy outages, and it is one command: caddy validate --config /etc/caddy/Caddyfile. It parses the whole file and answers whether Caddy would accept it — before you ask the running server to swap to it. A reply of Valid configuration is the green light; anything else means you have not yet changed what the outside world sees, which is exactly the property you want from a failed step.

Then systemctl reload caddy, which applies the new config with zero downtime and confirm systemctl is-active caddy still returns active. Reload, never blind-restart against an unvalidated file: a restart that fails to parse leaves the site down for a typo.

4. The certificate: proving it, not assuming it

Here is where automatic HTTPS earns or loses your trust. Two facts decide whether the challenge succeeds, and both are outside Caddy:

  • DNS has to point at this machine, and ports 80/443 must be reachable from the public internet. Let's Encrypt reaches your domain to verify it. A firewall that drops 80, a cloud security group that never opened 443, a record still pointing at the old host — any one of these turns “automatic” into a silent retry loop.
  • The rate limits are real and unforgiving. A run of failed issuances for the same domain and you are locked out for hours. This is why you fix the DNS and firewallfirst, and why Caddy's staging behaviour exists — to exercise the path without spending live quota.

The verification that matters is an actual TLS handshake from outside, once DNS resolves — a curl -sI https://example.comreturning a certificate Caddy issued, not the distribution's default page and not a self-signed placeholder. The renewal you do not have to configure is the whole point: Caddy renews in the background, well before expiry, for as long as the service runs.

What Caddy does not do for you

Terminating TLS does nothing for the security of the application behind it — a vulnerable app behind a perfect certificate is still a vulnerable app. Automatic HTTPS is not a firewall; it opens the very ports it needs and secures nothing else. It cannot conjure a certificate for a domain that does not resolve to the machine, and it will not paper over a cloud security group you forgot to open. And on-demand TLS — issuing certificates for arbitrary hostnames as requests arrive — is powerful and a foot-gun in equal measure: without an ask endpoint that says which hostnames are allowed, it will happily try to issue for anything pointed at you, straight into a rate limit. Convenience is not a security posture.

The check that proves HTTPS is real

In order: caddy version for the binary; caddy validate --config /etc/caddy/Caddyfile returning Valid configuration; systemctl is-active caddy at active and is-enabled so it survives a reboot; ss -tlpnconfirming Caddy — and only Caddy — holds 80 and 443; and, once DNS resolves, a real external HTTPS handshake against the domain. Prove the certificate from outside the box, because that is the only vantage point that matches your users'.

Where Servor fits

Servor ships Caddy as a recipe — one of 150+ vetted playbooks — and the recipe shape is exactly what an ACME-driven task needs. It is not a frozen script fired blindly: it starts with reconnaissance, reading whether Caddy is already installed, whether 80 and 443 are free, and whether a config exists, so the run adapts to a real server instead of assuming an empty one. A cheap model drives it, and every step it proposes carries its own verify — the caddy validate, the is-active, the version string — so success is proven, not asserted. The playbook is idempotent: an existing Caddy is checked and reported rather than reinstalled, and it will not overwrite your Caddyfile without a backup. If no domain is supplied, it serves on :80 without TLS rather than failing on issuance.

The execution model is the same one that governs the copilot. Every command is signed in your browser with a key derived from your vault key, relayed verbatim, and verified by the agent on the machine before it runs — the Caddy recipe bends none of the zero-knowledge rules. The agent reaches the control plane over an outbound connection, so opening 80 and 443 to the world never touches your management path.

If you would rather run the certbot path against an existing nginx or Apache, that is a recipe too; the tradeoffs between a reverse proxy and its TLS are the subject of the nginx reverse-proxy piece, and the discipline behind the approve-sign-verify loop is in the Plan-Execute-Verify piece. Browse the rest of the recipe catalogue, or read how the operational half fits together in our server operations guide.

Next step

Run it, don't just read it.

Free for two servers, no card. Every command is signed in your browser before it runs — our servers relay it, they cannot forge it.

Continue reading

Archive →