JuliusBrussee/caveman · critical
production requires an https:// CLICKHOUSE_URL (TLS only); C
Error message
production requires an https:// CLICKHOUSE_URL (TLS only); ClickHouse credentials travel as HTTP Basic and a plaintext endpoint ships them in the clear
What it means
Thrown by ValidateProduction in shared/platform/chhttp/chhttp.go:317 when the process runs in production mode (per IsProduction) and CLICKHOUSE_URL is unset, unparseable, not https-scheme, or has an empty host. The rationale in the code: ClickHouse credentials travel as HTTP Basic auth, so a plaintext http:// endpoint would ship them in the clear. This gate turns what would be an errTransport refusal at request time into a loud non-zero exit at boot.
Source
Thrown at shared/platform/chhttp/chhttp.go:317
// It refuses, in production, a non-HTTPS CLICKHOUSE_URL (the ClickHouse password
// travels as HTTP Basic, so plaintext ships it in the clear) and any attempt to
// disable certificate verification. It also resolves the TLS configuration
// eagerly so an unreadable or unparseable CA bundle fails the process at boot
// instead of at the first telemetry flush. Outside production it warns — loudly
// and once — when verification has been switched off.
//
// It is NOT guaranteed to run before every client is constructed: control-api
// builds a package-level query client at init (internal/httpapi/chclients.go), so
// that one exists before main calls anything. That ordering is safe rather than
// lucky — a client built from a rejected configuration carries errTransport and
// refuses to send, so the failure is the refusal either way; ValidateProduction's
// job is to turn it into a loud non-zero exit at boot instead of a runtime error.
func ValidateProduction(logger *slog.Logger) error {
if production() {
raw := strings.TrimSpace(env.String("CLICKHOUSE_URL", ""))
parsed, err := url.Parse(raw)
if err != nil || !strings.EqualFold(parsed.Scheme, "https") || parsed.Hostname() == "" {
return fmt.Errorf("production requires an https:// CLICKHOUSE_URL (TLS only); ClickHouse credentials travel as HTTP Basic and a plaintext endpoint ships them in the clear")
}
}
cfg, err := tlsClientConfig()
if err != nil {
return err
}
if cfg != nil && cfg.InsecureSkipVerify && logger != nil {
logger.Warn("ClickHouse TLS certificate verification is DISABLED",
"variable", skipVerifyEnv,
"env", env.String("CAVE_ENV", "local"),
"impact", "telemetry and aggregate queries can be read or forged by any in-path party; never set this in production")
}
return nil
}
View on GitHub (pinned to 27d5a3981a)
Solutions
- Set CLICKHOUSE_URL to an https:// endpoint, e.g. https://clickhouse.internal:8443.
- Enable native TLS on ClickHouse (openSSL server config, https_port 8443) or put it behind an HTTPS-terminating proxy and point the URL there.
- If the value looked set but still fails, print it masked: the scheme and host must survive url.Parse (watch quotes/whitespace in env files).
- Never route around with skip-verify plus http - the check is scheme-based and http is refused regardless.
Example fix
# before CAVE_ENV=prod CLICKHOUSE_URL=http://clickhouse:8123 # after CAVE_ENV=prod CLICKHOUSE_URL=https://clickhouse.internal:8443
Defensive patterns
Strategy: validation
Validate before calling
// deploy-time check mirroring the gate
u, err := url.Parse(os.Getenv("CLICKHOUSE_URL"))
if err != nil || !strings.EqualFold(u.Scheme, "https") || u.Hostname() == "" {
return errors.New("CLICKHOUSE_URL must be https:// with a host before prod deploy")
} Try / catch
if err := chhttp.ValidateProduction(logger); err != nil {
log.Fatalf("refusing to start: %v", err) // non-zero exit at boot is the intended contract
} Prevention
- Run ValidateProduction in a pre-deploy smoke stage against prod env, not only at container start.
- Add a CI policy rule that rejects http:// CLICKHOUSE_URL in prod manifests.
- Terminate TLS at or before ClickHouse so the app always speaks https in prod.
When it happens
Trigger: CAVE_ENV resolves to production while CLICKHOUSE_URL is http://clickhouse:8123, localhost:8123 (no scheme), empty, or a URL whose parse fails or whose hostname is blank. Any of these returns this error from ValidateProduction.
Common situations: Promoting a compose/staging config to prod without switching the ClickHouse endpoint to TLS; ClickHouse deployed without TLS and fronted by nothing; a proxy terminator added later so the app still speaks http internally; env var lost during secret migration.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- production refuses %s=true: it disables chain and hostname v
- production KMS configuration: %w
- production refuses default or empty %s
- production requires %s to contain at least 32 characters
- production requires CAVE_PUBLIC_URL to be an HTTPS origin wi
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/0b93e78ed75bdfe0.
Report an issue: GitHub.