oauth2-proxy/oauth2-proxy · error

could not load certificate: %v

Error message

could not load certificate: %v

What it means

Wrap error in setupTLSListener: getCertificate failed while loading the TLS key/cert pair for the HTTPS listener (nil secret source, unreadable file, or X509KeyPair parse failure), so TLS cannot be set up at startup.

Source

Thrown at pkg/proxyhttp/server.go:195

// The HTTPS server can be disabled by setting the SecureBindAddress to "-" or by
// leaving it empty.
func (s *server) setupTLSListener(opts Opts) error {
	if opts.SecureBindAddress == "" || opts.SecureBindAddress == "-" {
		// No HTTPS listener required
		return nil
	}

	config := &tls.Config{
		MinVersion: tls.VersionTLS12, // default, override below
		MaxVersion: tls.VersionTLS13,
		NextProtos: []string{"http/1.1"},
	}
	if opts.TLS == nil {
		return errors.New("no TLS config provided")
	}
	cert, err := getCertificate(opts.TLS)
	if err != nil {
		return fmt.Errorf("could not load certificate: %v", err)
	}
	config.Certificates = []tls.Certificate{cert}

	if len(opts.TLS.CipherSuites) > 0 {
		cipherSuites, err := parseCipherSuites(opts.TLS.CipherSuites)
		if err != nil {
			return fmt.Errorf("could not parse cipher suites: %v", err)
		}
		config.CipherSuites = cipherSuites
	}

	if len(opts.TLS.MinVersion) > 0 {
		switch opts.TLS.MinVersion {
		case "TLS1.2":
			config.MinVersion = tls.VersionTLS12
		case "TLS1.3":
			config.MinVersion = tls.VersionTLS13
		default:

View on GitHub (pinned to 33c2eb92de)

Solutions

  1. Read the wrapped error to identify the root cause (file not found, PEM decode failure, key mismatch)
  2. Verify CertFile and KeyFile paths exist and are readable: test with `openssl x509 -in cert.pem` and `openssl rsa -in key.pem -check`
  3. Confirm the cert and key belong together by comparing their public key moduli
  4. If using Secret/mounted secrets, confirm the mount happened and permissions allow the process to read them

Example fix

// before
server, _ := NewServer(ctx, Opts{TLS: &TLS{CertFile: "/etc/ssl/cert.pem", KeyFile: "/etc/ssl/key.pem"}}) // key not mounted
// after
server, _ := NewServer(ctx, Opts{TLS: &TLS{CertFile: "/etc/ssl/tls/tls.crt", KeyFile: "/etc/ssl/tls/tls.key"}}) // verified mount
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight check mirroring getCertificate
if _, err := tls.LoadX509KeyPair("/etc/ssl/tls.crt", "/etc/ssl/tls.key"); err != nil {
    return fmt.Errorf("TLS keypair unusable before server start: %w", err)
}

Type guard

func certFilesReadable(certFile, keyFile string) bool {
    c, err1 := os.ReadFile(certFile)
    k, err2 := os.ReadFile(keyFile)
    return err1 == nil && err2 == nil &&
        bytes.Contains(c, []byte("BEGIN CERTIFICATE")) &&
        (bytes.Contains(k, []byte("PRIVATE KEY")))
}

Try / catch

srv, err := NewServer(ctx, opts)
if err != nil && strings.Contains(err.Error(), "could not load certificate") {
    return fmt.Errorf("check TLS cert/key paths, permissions, and that they form a matching pair: %w", err)
}

Prevention

When it happens

Trigger: NewServer → setupTLSListener where opts.TLS is non-nil but getCertificate fails because KeyFile/CertFile do not exist, are unreadable, are not valid PEM, or the key does not match the certificate.

Common situations: Wrong paths in container images (file not copied), expired/reissued certs replaced without updating the key, concatenated files with trailing garbage, running as a user without read permission on the key.

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 oauth2-proxy/oauth2-proxy@33c2eb92de (2026-09-06). Data as JSON: /api/errors/03da1038f48ad07a. Report an issue: GitHub.