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

  1. Append sslmode=verify-full to the DATABASE_URL query string
  2. Also configure the CA certificate (the next check requires a pinned CA) so verify-full can validate the server chain
  3. 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

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


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)