caddyserver/caddy · error
got HTTP %d
Error message
got HTTP %d
What it means
Returned at handshake time by HTTPCertGetter.GetCertificate when the remote certificate endpoint answered with a status code other than 200 or 204. 204 is treated as 'this endpoint is not managing certs for this handshake' and returns nil; any other non-200 status is an error because the body cannot be trusted to contain a valid PEM bundle.
Source
Thrown at modules/caddytls/certmanagers.go:168
}
parsed.RawQuery = qs.Encode()
req, err := http.NewRequestWithContext(hcg.ctx, http.MethodGet, parsed.String(), nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req) //nolint:gosec // SSRF false positive... request URI comes from config
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNoContent {
// endpoint is not managing certs for this handshake
return nil, nil
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("got HTTP %d", resp.StatusCode)
}
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %v", err)
}
cert, err := tlsCertFromCertAndKeyPEMBundle(bodyBytes)
if err != nil {
return nil, err
}
return &cert, nil
}
// UnmarshalCaddyfile deserializes Caddyfile tokens into ts.
//
// ... http <url>View on GitHub (pinned to 50e54ee279)
Solutions
- Reproduce the handshake request manually (include the serial/SNI query params) against the URL and inspect the status code and body
- If the endpoint genuinely has no cert for this SNI yet, return 204 instead of 404 so the next getter in the chain can be tried
- Fix auth/proxy issues: verify tokens, upstream health, and that the route still exists
- Check Caddy's logs for which URL and handshake triggered it, since the error carries only the status number
Example fix
// before (cert endpoint handler)
func certHandler(w http.ResponseWriter, r *http.Request) {
cert := lookup(r.URL.Query().Get("server_name"))
if cert == nil {
http.NotFound(w, r) // 404 -> handshake error in Caddy
return
}
w.Write(cert)
}
// after
func certHandler(w http.ResponseWriter, r *http.Request) {
cert := lookup(r.URL.Query().Get("server_name"))
if cert == nil {
w.WriteHeader(http.StatusNoContent) // 204: try next source
return
}
w.Write(cert)
} Defensive patterns
Strategy: try-catch
Try / catch
// If wrapping tls.get_certificate.http behind your own getter:
cert, err := hcg.GetCertificate(ctx, hello)
if err != nil {
if strings.HasPrefix(err.Error(), "got HTTP ") {
// endpoint responded but not usefully; log and rethrow or fall through
log.Printf("cert endpoint status: %v", err)
}
return nil, err
} Prevention
- Make your cert endpoint return 204 (not 404) when it has no cert for a handshake, so chains can continue
- Health-check the cert endpoint independently of handshakes to catch auth/proxy failures early
- Keep the URL pinned to a stable route; avoid redirects on the cert path
When it happens
Trigger: The configured URL returns 404 (no cert for the requested SNI/serial), 401/403 (auth failure), 500 (upstream bug), or a redirect chain ending in a non-200. The request includes query params for the ClientHello (serial, SNI, cipher suites), so endpoints that key on those params can 404 legitimately.
Common situations: Cert management service does not have a certificate issued yet for the domain; auth token expired or missing on the getter side; the endpoint route changed after an upgrade of the cert service; a proxy in front returns 502/503.
Related errors
- HTTP %d fetching CA certificate bundle from %s
- URL is required
- error reading response body: %v
- server listening on %v is HTTP, but attempts to configure TL
- two policies with same match criteria have conflicting cert
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/6a7db9e96dc1f524.
Report an issue: GitHub.