caddyserver/caddy · critical
finalizing automatic HTTPS: %v
Error message
finalizing automatic HTTPS: %v
What it means
After all servers are listening, automaticHTTPSPhase2 begins actual certificate management (issuing/renewing via ACME/Internal CAs, solving challenges, loading managed certs). Any failure in that startup — a CA rejecting issuance, challenge solver failure, cache errors — is wrapped as 'finalizing automatic HTTPS'. The server sockets are already open, but Caddy aborts Start.
Source
Thrown at modules/caddyhttp/app.go:670
// Can only serve h3 with TLS enabled
app.logger.Warn("HTTP/3 skipped because it requires TLS",
zap.String("network", listenAddr.Network),
zap.String("addr", hostport))
}
}
}
}
srv.logger.Info("server running",
zap.String("name", srvName),
zap.Strings("protocols", srv.Protocols))
}
// finish automatic HTTPS by finally beginning
// certificate management
err := app.automaticHTTPSPhase2()
if err != nil {
return fmt.Errorf("finalizing automatic HTTPS: %v", err)
}
return nil
}
// stdlibLogPrefixPanic is the prefix Go's net/http server uses when it writes a
// recovered handler panic (and its stack trace) to http.Server.ErrorLog.
// See the deferred recover in net/http.(*conn).serve.
const stdlibLogPrefixPanic = "http: panic serving"
// serverErrorLogger returns a *log.Logger suitable for http.Server.ErrorLog
// that forwards the standard library's server messages to Caddy's structured
// logger. Most of these messages are low-signal and logged at DEBUG, but
// recovered handler panics indicate a real server-side error, so they are
// logged at ERROR to stay visible at the default log level.
func serverErrorLogger(logger *zap.Logger) *log.Logger {
return log.New(stdlibLogRouter{logger: logger}, "", 0)
}View on GitHub (pinned to 50e54ee279)
Solutions
- Read the wrapped error to identify the failing CA/domain/challenge
- For local/test use, force the internal CA: `tls internal` on that site so no external ACME is attempted
- Open/forward ports 80+443 or switch to a DNS-01 challenge with a provider plugin; wait out rate limits or use Let's Encrypt staging while testing
Example fix
# before
example.com {
reverse_proxy localhost:8080
}
# after (local testing)
example.com {
tls internal
reverse_proxy localhost:8080
} Defensive patterns
Strategy: fallback
Validate before calling
// pre-flight DNS check for ACME-managed domains
for _, d := range managedDomains {
addrs, err := net.LookupHost(d)
if err != nil || len(addrs) == 0 {
return fmt.Errorf("domain %s does not resolve; issuance will fail", d)
}
} Try / catch
if err := caddy.Run(cfg); err != nil {
if strings.Contains(err.Error(), "finalizing automatic HTTPS") {
// fallback: rerun with tls internal / ACME staging, or surface the wrapped cause
}
} Prevention
- Point DNS at the server and open 80+443 before first start
- Use Let's Encrypt staging (`acme_ca`) while testing to dodge rate limits
- Use `tls internal` for anything not needing public trust; disable auto_https for pure HTTP backends
When it happens
Trigger: ACME account/issuance errors (e.g. Let's Encrypt rate limit, unreachable DNS), failing HTTP-01/TLS-ALPN challenges because ports 80/443 are not externally reachable, an explicitly configured internal CA that fails to initialize, or on-demand cert config errors.
Common situations: First deploy behind a firewall blocking port 80 so HTTP-01 fails; DNS for the domain not yet pointing at the server; staging->production LE quota exhaustion; test environments without internet access.
Related errors
- automate: managing %v: %v
- two policies with same match criteria have conflicting cert
- unable to add %s to trust pool: %v
- URL is required
- got HTTP %d
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/e7a8274325ce855a.
Report an issue: GitHub.