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

  1. Reproduce the handshake request manually (include the serial/SNI query params) against the URL and inspect the status code and body
  2. 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
  3. Fix auth/proxy issues: verify tokens, upstream health, and that the route still exists
  4. 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

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


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/6a7db9e96dc1f524. Report an issue: GitHub.