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:
A verifying mode with no CAs to verify against also fails closed:
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 readsr.TLS.VerifiedChains. - gRPC:
transportgrpc.WithGRPCMTLSVerifier(authn.CertVerifier), which reads the chains frompeer.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.