NoctisNode
← GuidesSecurity2026-10-2310 min

Caddy Automatic HTTPS: How Caddy Auto TLS Works and Fixes

Caddy gets and renews TLS certificates via ACME for every domain in your config. Learn HTTP-01, TLS-ALPN-01, DNS-01 wildcards, on-demand TLS and common failures.

Short answer: Caddy enables HTTPS automatically for any site address that is a public domain name. It requests a certificate over ACME (Let's Encrypt or ZeroSSL by default), proves control of the domain via the HTTP-01 or TLS-ALPN-01 challenge on ports 80 and 443, redirects HTTP to HTTPS, and renews the certificate before it expires, with no extra configuration.

A Caddy reverse proxy is simply Caddy terminating that HTTPS connection and forwarding the request to an app on another port, container or host using the `reverse_proxy` directive. That combination, TLS plus proxying in a few lines, is the main reason people choose it.

Reverse proxy with SSL in three lines

With DNS pointing at the server and ports 80 and 443 reachable, this Caddyfile is a complete HTTPS reverse proxy for two hostnames. Caddy obtains certificates for both names on startup, serves HTTP/1.1, HTTP/2 and HTTP/3, and passes `X-Forwarded-For`, `X-Forwarded-Proto` and `X-Forwarded-Host` to the backend. The `email` option is optional but lets the CA contact you about expiring or revoked certificates.

Installation from the official apt repository and systemd basics are covered in the Caddy reverse proxy guide; for containers see Caddy as a Docker reverse proxy. Apply changes with `sudo systemctl reload caddy`.

# /etc/caddy/Caddyfile
{
	email [email protected]
}

example.com, www.example.com {
	reverse_proxy 127.0.0.1:8080
}

How Caddy auto TLS works under the hood

When Caddy loads a config, it collects every hostname from site addresses and decides who should issue each certificate. Public domains go to the public ACME issuers; `localhost`, IP addresses and internal-only names get certificates from Caddy's own local CA (the same as writing `tls internal`). Certificates, keys and the ACME account are stored in Caddy's data directory: `/var/lib/caddy/.local/share/caddy` for the apt package, `/data` in the official Docker image.

Renewal happens in the background well before expiry, and Caddy staples OCSP responses where the CA supports them. If issuance fails, Caddy retries with exponential backoff and uses the CA's staging endpoint for retries to avoid burning production rate limits. Multiple Caddy instances sharing the same storage coordinate so only one requests a given certificate.

ChallengeWhat the CA checksNeedsWildcards?
HTTP-01A token served at `http://domain/.well-known/acme-challenge/`Port 80 reachable from the internetNo
TLS-ALPN-01A special certificate presented on a TLS handshakePort 443 reachable from the internetNo
DNS-01A TXT record at `_acme-challenge.domain`DNS provider API token and a Caddy build with that provider moduleYes

Wildcard certificates with DNS-01

Wildcard certificates such as `*.example.com` can only be issued through the DNS-01 challenge. The standard Caddy binary does not include DNS provider plugins, so you build one with xcaddy (or pick the provider on the official download page) and give Caddy an API token limited to editing DNS for that zone. DNS-01 also works when ports 80 and 443 are not reachable from the internet, for example on an internal-only server.

Keep the token out of the Caddyfile: put it in an environment file used by the systemd unit (`sudo systemctl edit caddy`, then `EnvironmentFile=/etc/caddy/caddy.env` under `[Service]`) and restrict the file with `chmod 600`.

# Build Caddy with a DNS provider module (example: Cloudflare)
xcaddy build --with github.com/caddy-dns/cloudflare

# Caddyfile: one wildcard certificate for all subdomains
*.example.com {
	tls {
		dns cloudflare {env.CF_API_TOKEN}
	}

	@app host app.example.com
	handle @app {
		reverse_proxy 127.0.0.1:3000
	}

	handle {
		respond "Unknown host" 404
	}
}

On-demand TLS and staging

On-demand TLS makes Caddy obtain a certificate during the first TLS handshake for a hostname it has not seen before. It is meant for platforms where customers point their own domains at your server. Always configure the `ask` endpoint: Caddy calls it with `?domain=name` and only proceeds on a 200 response. Without it, anyone could point random names at your IP and make you hit rate limits.

While experimenting, switch to the Let's Encrypt staging CA with the global `acme_ca` option. Staging certificates are not trusted by browsers, but the limits are far higher, so you can break and fix things freely. Remove the option, and optionally delete the staging certificates from the data directory, when you are done.

{
	on_demand_tls {
		ask http://127.0.0.1:5555/allowed
	}
}

https:// {
	tls {
		on_demand
	}
	reverse_proxy 127.0.0.1:8080
}

# Global options block for testing
{
	email [email protected]
	# Test against Let's Encrypt staging; remove when everything works
	acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}

Common Caddy TLS failures and how to fix them

  1. 01.DNS not pointed: the A or AAAA record is missing, still points to an old server, or is cached. A stale AAAA record is a classic trap, because the CA prefers IPv6 when it exists. Check both with `dig`.
  2. 02.Port 80 or 443 closed: the host firewall, a cloud firewall or a router blocks inbound traffic, so neither HTTP-01 nor TLS-ALPN-01 can complete. Open 80/tcp and 443/tcp (and 443/udp for HTTP/3).
  3. 03.Port already in use: Apache, nginx or another container is bound to 80 or 443, so Caddy cannot listen. Stop the other service or move it behind Caddy.
  4. 04.CDN proxy in front: an orange-cloud style proxy terminates TLS before Caddy, which can break TLS-ALPN-01. Use DNS-01, or set the record to DNS-only during issuance.
  5. 05.Rate limits: repeated failed attempts or wiping the data directory make you hit CA limits (Let's Encrypt limits failed validations per hostname per hour and duplicate certificates per week). Fix the root cause, use staging, and keep the data directory persistent.
  6. 06.CAA record: a CAA record that only allows a different CA blocks issuance. Allow `letsencrypt.org` and `sectigo.com` (used by ZeroSSL) or remove the restriction.
# Does the name resolve to this server (both IPv4 and IPv6)?
dig +short A example.com
dig +short AAAA example.com
curl -4 -s https://ifconfig.me ; echo

# Are ports 80 and 443 open in the host firewall?
sudo ufw status
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp

# Is something else already bound to 80/443?
sudo ss -ltnp '( sport = :80 or sport = :443 )'

# What does Caddy say?
sudo journalctl -u caddy --no-pager -n 100

HTTPS to the backend, too

Most backends on the same host speak plain HTTP on localhost, which is fine. When the upstream is on another machine or insists on TLS, use an `https://` upstream and tell Caddy which name to verify and which CA to trust. Avoid `tls_insecure_skip_verify` outside quick tests, because it removes protection against interception between proxy and backend. For traffic between servers, a private WireGuard tunnel is often simpler than managing backend certificates.

On a NoctisNode VPS with full root and a dedicated IPv4 address, the usual setup is: point an A record (no AAAA unless the server has IPv6) at the server, open 80 and 443, and let Caddy handle the rest. If you need to forward non-HTTP traffic, see Caddy TCP and SSH proxying.

example.com {
	reverse_proxy https://10.0.0.5:8443 {
		transport http {
			tls_server_name backend.internal
			tls_trust_pool file /etc/caddy/backend-ca.pem
		}
	}
}

FAQ

QWhat is a Caddy reverse proxy?

AIt is the Caddy web server accepting client connections, usually terminating HTTPS, and forwarding requests to one or more backend services with the `reverse_proxy` directive. Caddy obtains and renews the TLS certificates for it automatically.

QDoes Caddy renew certificates automatically?

AYes. Caddy renews certificates in the background well before they expire and reloads them without a restart, as long as the domain still resolves to the server and the challenge ports stay reachable.

QWhy is Caddy not getting a certificate?

AThe most common causes are DNS records pointing elsewhere (including a stale AAAA record), ports 80 and 443 blocked, another program using those ports, or rate limits after repeated failures. `journalctl -u caddy` shows the exact ACME error.

QCan Caddy issue a wildcard certificate?

AYes, but only through the DNS-01 challenge, which requires a Caddy build that includes your DNS provider's module and an API token for that zone.

QHow do I disable automatic HTTPS in Caddy?

AUse an `http://` site address for a single site, or set the global option `auto_https off`. You can also use `auto_https disable_redirects` to keep certificates but stop the HTTP-to-HTTPS redirect.

Caddy Automatic HTTPS: How Caddy Auto TLS Works and Fixes · NoctisNode