Skip to content

TLS settings reference

transport does not own any TLS types. Certificates, hardened defaults and the client-certificate policy come from go/tls, and this module decides when to apply them. This page is the map of which knob applies on which call path, and which one wins when two of them disagree.

The four ways to give a server TLS

Route Package Where it is accepted Certificate source
WithTLSPair(gtls.Pair) transport/http, transport/grpc Register only (a RegisterOption) file paths, loaded at start
StartWithTLSPair / Start transport/http, transport/grpc the hand-wired lifecycle path file paths, loaded at start
WithServedCertificate(*tls.Certificate) transport/http only NewServer and Register (a ServerOption) in memory, applied at construction
grpc.Creds(...) transport/grpc any of the option variadics whatever you build, e.g. via TLSServerCredentials

WithServerTLSConfig(*tls.Config) is not in that list because it does not, on its own, turn TLS on for a gtls.Pair-driven server — it replaces the base config that one of the routes above then contributes a certificate to.

There is no WithServedCertificate for gRPC

The in-memory-certificate route exists only on the HTTP side. A gRPC server with a certificate held in RAM has to go through grpc.Creds(credentials.NewTLS(cfg)) with the certificate placed on cfg yourself.

When does the server actually serve TLS

HTTP. TLS is served when either the pair is enabled or the server's TLS config already carries certificate material — a static certificate, a GetCertificate callback, or a GetConfigForClient hook. Populated certificate material is treated as TLS intent, and the hardened default config carries none, so a plain server is unambiguous. This is why WithServedCertificate needs no accompanying "enable TLS" flag, and why a certificate placed directly on a WithServerTLSConfig is also served.

gRPC. TLS is served only when the pair's Enabled field is true. There is no certificate-material inference on this side.

On both, a pair with Enabled: false means plaintext even when Cert and Key are populated — so a config-driven caller can pass a fully-populated but disabled pair to mean "not today".

gtls.Pair fields this module reads

type Pair struct {
    Enabled   bool     `mapstructure:"enabled"     yaml:"enabled"     json:"enabled"`
    Cert      string   `mapstructure:"cert"        yaml:"cert"        json:"cert"`
    Key       string   `mapstructure:"key"         yaml:"key"         json:"key"`
    ClientCAs []string `mapstructure:"client_cas"  yaml:"client_cas"  json:"client_cas"`
    ClientAuth string  `mapstructure:"client_auth" yaml:"client_auth" json:"client_auth"`
}
Field Default Effect When it is wrong
Enabled false Gates TLS on the pair path. A disabled pair with a cert and key serves plaintext, silently. That is the intended meaning, not a bug.
Cert "" PEM certificate (chain) file path. Missing or unreadable → start fails with loading TLS configuration (HTTP) / configuring gRPC TLS (gRPC).
Key "" PEM private key file path. As above. Loading is synchronous at start, so a bad certificate fails the controller rather than the serve goroutine.
ClientCAs nil PEM CA files that client certificates must chain to. A file with no PEM certificates in it → no certificates found in "<path>".
ClientAuth "" Client-certificate enforcement mode. See the table below.

ClientAuth modes

Value crypto/tls mode Meaning
"" with ClientCAs set RequireAndVerifyClientCert Implies require-verify. Setting CAs is taken as intent to enforce.
"" without ClientCAs NoClientCert No client-certificate authentication.
"request" RequestClientCert Ask for a certificate; neither require nor verify it.
"verify-if-given" VerifyClientCertIfGiven Do not require one, but verify any that is presented — a mixed-auth listener.
"require-verify" RequireAndVerifyClientCert Require a certificate that chains to ClientCAs.

Anything else fails closed:

unknown client_auth "<value>" (valid: "request", "verify-if-given", "require-verify")

A verifying mode with no CAs to verify against also fails closed:

client_auth "<value>" requires client_cas to verify against

A typo therefore never silently downgrades mTLS to "off" — it stops the server starting.

Which TLS setting wins when two are supplied

A pair is merged into the base config, never substituted for it. The merge is gtls.Pair.ApplyTo on a clone, so the config you supplied is not mutated:

Concern Winner Detail
Certificate both The pair's certificate is appended to Certificates, alongside anything already there.
MinVersion, cipher suites, curves, verification hooks your config Everything you set on a WithServerTLSConfig survives the merge.
ClientCAs / ClientAuth your config, if it has any The pair's client-certificate policy is applied only when the config carries ClientAuth == NoClientCert and a nil ClientCAs. Otherwise the pair's ClientCAs/ClientAuth are ignored.
Invalid ClientAuth on the pair neither — it errors An invalid mode errors even when the config's policy would have won, so a typo is never masked.
ALPN additive On the gRPC listener h2 is appended unless already advertised. Nothing is removed.
grpc.Creds(...) versus WithTLSPair your credentials, for credential selection But the listener is still TLS-wrapped — see the warning below.

With no WithServerTLSConfig, the base is gtls.DefaultConfig(): TLS 1.2 minimum, six AEAD cipher suites (ECDHE with AES-GCM and ChaCha20-Poly1305), and X25519 then P-256 curve preferences. Those specifics belong to go/tls and can change there.

Do not combine an enabled pair with your own grpc.Creds

On the gRPC Register path an enabled pair wraps the listener in tls.NewListener and installs internal credentials that surface the listener's TLS state to peer.FromContext. Those credentials are prepended, so a caller-supplied grpc.Creds(...) replaces them — but the listener is still TLS-wrapped, and your credentials then attempt a second handshake inside the established session. Clients fail with error reading server preface: EOF. Choose one route.

Why mTLS needs a verifier as well as a policy

ClientCAs and ClientAuth decide whether the TLS handshake accepts the connection. They say nothing about who the peer is. Authenticating the peer from its certificate is a separate step:

  • HTTP: transporthttp.WithMTLSVerifier(authn.CertVerifier), which reads r.TLS.VerifiedChains.
  • gRPC: transportgrpc.WithGRPCMTLSVerifier(authn.CertVerifier), which reads the chains from peer.FromContext.

On the gRPC managed-TLS path this only works because the listener's TLS state is surfaced as credentials.TLSInfo; a raw tls.NewListener on its own leaves AuthInfo nil and a verified client certificate invisible to the RPC layer.

Setting a client-certificate policy without a verifier gives you a connection the handshake trusted and no identity. Setting a verifier without a policy gives you a verifier that never fires, because there are no verified chains to read.

Client-side helpers

Function Package Use
TLSClientCredentials(caFiles...) transport/grpc gRPC client credentials trusting the given PEM CA/certificate files; with no files, the system roots.
TLSServerCredentials(certFile, keyFile) transport/grpc Server credentials for the grpc.Creds route. Ignores Enabled — it takes paths, not a pair.

For an HTTP client, use go/httpclient; this module is server-side only.