JuliusBrussee/caveman · error
postgres: production DATABASE_URL requires…
Error message
postgres: production DATABASE_URL requires sslmode=verify-full
What it means
In production, ParsePoolConfig requires the DATABASE_URL query string to contain sslmode=verify-full. Without verify-full, lib/pq/pgx will encrypt but not authenticate the server, enabling machine-in-the-middle attacks; the library treats anything weaker as an invalid production configuration.
Solutions
- Append sslmode=verify-full to the DATABASE_URL query string
- Also configure the CA certificate (the next check requires a pinned CA) so verify-full can validate the server chain
- If the database lacks a verifiable TLS certificate, fix the server cert instead of downgrading sslmode
Example fix
// before DATABASE_URL="postgres://app:secret@db.internal:5432/prod?sslmode=require" // after DATABASE_URL="postgres://app:secret@db.internal:5432/prod?sslmode=verify-full&sslrootcert=/etc/certs/ca.pem"
Defensive patterns
Strategy: validation
Validate before calling
u, _ := url.Parse(databaseURL)
if u.Query().Get("sslmode") != "verify-full" {
return fmt.Errorf("production DATABASE_URL must set sslmode=verify-full")
} Type guard
func hasVerifyFull(raw string) bool {
u, err := url.Parse(raw)
return err == nil && u.Query().Get("sslmode") == "verify-full"
} Try / catch
cfg, err := postgresconfig.ParsePoolConfig(databaseURL)
if err != nil && strings.Contains(err.Error(), "sslmode=verify-full") {
log.Fatal("production database requires sslmode=verify-full; refusing to start")
} Prevention
- Standardize production connection strings on ?sslmode=verify-full
- Never copy staging connection strings into production manifests unreviewed
- Add a CI/lint check that production secret templates include sslmode=verify-full
- Provision server certificates that pass full verification so verify-full is viable
When it happens
Trigger: Production call to NewPool/ParsePoolConfig with sslmode=require, prefer, disable, verify-ca, or with sslmode missing entirely from the URL query.
Common situations: Copy-pasting a connection string from a cloud dashboard that defaults to sslmode=require; migrating from staging where weaker sslmode was accepted; stripping query params when building the URL programmatically.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- postgres: production connection includes a plaintext…
- postgres: CA certificate configured while TLS is disabled
- postgres: production DATABASE_URL must be a Postgres URL
- postgres: read
- postgres: contains no valid certificate
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/be48325390daf511.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/postgresconfig/postgresconfig.go:37
)
const (
caEnvironment = "CAVE_POSTGRES_CA_CERT"
caFileEnvironment = "CAVE_POSTGRES_CA_CERT_FILE"
)
// ParsePoolConfig validates the connection string and applies the managed
// database CA to every TLS path. Production rejects sslmode=require because it
// encrypts without authenticating the server; verify-full is mandatory.
func ParsePoolConfig(databaseURL string) (*pgxpool.Config, error) {
production := runtimeenv.IsProduction()
if production {
parsed, err := url.Parse(databaseURL)
if err != nil || (parsed.Scheme != "postgres" && parsed.Scheme != "postgresql") || parsed.Hostname() == "" {
return nil, errors.New("postgres: production DATABASE_URL must be a Postgres URL")
}
if parsed.Query().Get("sslmode") != "verify-full" {
return nil, errors.New("postgres: production DATABASE_URL requires sslmode=verify-full")
}
}
config, err := pgxpool.ParseConfig(databaseURL)
if err != nil {
return nil, fmt.Errorf("postgres: parse DATABASE_URL: %w", err)
}
caPEM, err := caPEMFromEnvironment()
if err != nil {
return nil, err
}
if production && caPEM == "" {
return nil, fmt.Errorf("postgres: %s or %s is required in production", caEnvironment, caFileEnvironment)
}
if caPEM == "" {
return config, nil
}
roots := x509.NewCertPool()View on GitHub (pinned to 3ee70a1026)