Skip to content

Configuration keys

Pair is the only configuration shape the package defines. It carries mapstructure, yaml and json tags with identical names, so the same block of config unmarshals the same way whichever loader you use.

tls:
  enabled: true
  cert: /etc/certs/server.pem
  key: /etc/certs/server-key.pem
  client_cas:
    - /etc/certs/client-ca.pem
  client_auth: require-verify

The package does not read config itself — it has no loader, no environment-variable prefix and no flags. You unmarshal into a Pair and hand it over. See Limitations.

Every key, its type and its default

Key Type Default What it does
enabled bool false Advisory flag read only by Pair.Valid. No builder consults it — see below.
cert string "" Path to the PEM certificate file (leaf first, then any intermediates).
key string "" Path to the PEM private key file for cert.
client_cas list of strings empty PEM files holding the CA certificates that client certificates must chain to. Turns on server-side mTLS.
client_auth string "" Client-certificate enforcement mode. One of request, verify-if-given, require-verify, or empty.

There are no other keys. Cipher suites, protocol versions and curves are not configurable through Pair — they come from DefaultConfig and are fixed.

What enabled: false actually does

Nothing, unless you check it. Pair.Valid is the only code path that reads Enabled:

func (p Pair) Valid() bool {
    return p.Enabled && p.Cert != "" && p.Key != ""
}

ServerConfig, ApplyTo and Certificate ignore it. A pair with enabled: false and valid paths still builds a working TLS config and still loads the certificate off disk. If a false should mean "serve plain HTTP", you have to branch on Valid() yourself before you build the config:

if !pair.Valid() {
    // serve plaintext, or refuse to start
}

What each client_auth value enforces

Value crypto/tls mode Client certificate client_cas required
request RequestClientCert Asked for, never required, never verified No
verify-if-given VerifyClientCertIfGiven Optional; verified when presented Yes
require-verify RequireAndVerifyClientCert Required and verified Yes
"" with client_cas set RequireAndVerifyClientCert Required and verified
"" with no client_cas NoClientCert Not asked for

The three non-empty values are also exported as constants — tls.ClientAuthRequest, tls.ClientAuthVerifyIfGiven, tls.ClientAuthRequireVerify — so Go callers do not have to spell the strings.

request is the one mode that accepts a certificate without checking it. Your handler receives it in r.TLS.PeerCertificates with VerifiedChains empty. Treat anything in it as unauthenticated input.

What happens when a key is wrong

Every failure below happens at config-build time — when you call ServerConfig, ApplyTo, ClientConfig or CertPool — not on the first handshake. A misconfigured service fails at startup.

Mistake Result
cert or key path does not exist Error: loading TLS certificate: open …: no such file or directory
cert and key do not match each other Error from crypto/tls, wrapped as loading TLS certificate: …
client_auth misspelled ("require_verify", "required", "REQUIRE-VERIFY") Error: unknown client_auth "…" (valid: "request", "verify-if-given", "require-verify"). Values are case-sensitive and hyphenated.
client_auth: require-verify or verify-if-given with no client_cas Error: client_auth "…" requires client_cas to verify against
A client_cas file is missing Error: reading CA file "…": open …: no such file or directory
A client_cas file exists but holds no PEM certificate Error: no certificates found in "…"
client_cas set, client_auth left empty Not an error — this means require-verify
enabled: false with valid paths Not an error — TLS is still configured; see above

The full list of messages, with the exact wording, is in Errors.

Sharing one certificate across several listeners

ResolvePair merges a shared pair with a per-transport pair, field by field, and PairOverrides says which fields the transport section actually set:

func ResolvePair(shared Pair, transport Pair, overrides PairOverrides) Pair
PairOverrides field Overrides
Enabled enabled
Cert cert
Key key
ClientCAs client_cas
ClientAuth client_auth

A field is taken from transport when its override boolean is true, and from shared otherwise. The mask exists because a transport section that omits enabled is indistinguishable from one that sets enabled: false once both have been unmarshalled into a struct — you decide what counts as "set" (a config key being present, a flag being changed) and express it as the boolean.

shared := tls.Pair{Enabled: true, Cert: "/etc/certs/shared.pem", Key: "/etc/certs/shared-key.pem"}
grpc := tls.Pair{Cert: "/etc/certs/grpc.pem"}

pair := tls.ResolvePair(shared, grpc, tls.PairOverrides{Cert: true})
// pair.Enabled == true, pair.Cert == "/etc/certs/grpc.pem", pair.Key == "/etc/certs/shared-key.pem"

ResolvePair reads no files and validates nothing. It cannot fail, and a nonsense result — say a cert from one transport and a key from another — surfaces later, as a certificate load error.

The resolved pair shares its client_cas slice with whichever pair supplied it: ResolvePair copies the slice header, not the backing array. Mutating resolved.ClientCAs[0] in place also mutates the shared pair. Build a new slice rather than editing one.