skip to content

Which tls.Config fields make a Go TLS server require and verify a client certificate?

level: juniorimportance: must knowfreq 42%

answer

  1. two fields, not one
  2. a trust set plus a policy
  3. the pool alone requests nothing
  4. the strictest ClientAuthType constant

basics

~10 s

Two fields on the server's tls.Config: ClientCAs, an x509.CertPool of the CAs you trust to issue client certificates, and ClientAuth set to tls.RequireAndVerifyClientCert. ClientCAs alone asks for nothing and verifies nothing.

solid answer

~50 s

On the server's `tls.Config` you set `ClientCAs` to an `*x509.CertPool` containing the certificate authorities you trust to issue client certificates, and `ClientAuth: tls.RequireAndVerifyClientCert`. `ClientAuth` is the policy — it decides whether the server asks for a certificate during the handshake and whether a missing or unverifiable one aborts it — while `ClientCAs` is only the trust set that verification runs against. Setting the pool without changing `ClientAuth` leaves the default `tls.NoClientCert`, so the server never even requests one. You attach the config to `http.Server.TLSConfig` and start with `ListenAndServeTLS`. Verification checks that the presented chain links to a CA in `ClientCAs`, that the certificates are inside their validity window, and that the leaf is usable for client authentication. It does not check who the caller is, so your handler still has to turn the certificate into an identity and authorise it.

code

go · 14 lines
go
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(caPEM) {
	return errors.New("no client CA certificates parsed")
}

srv := &http.Server{
	Addr: ":8443",
	Handler: mux,
	TLSConfig: &tls.Config{
		ClientCAs: pool,
		ClientAuth: tls.RequireAndVerifyClientCert,
	},
}
return srv.ListenAndServeTLS("server.crt", "server.key")

go deeper

for a junior

Be ready to name both fields without hesitation: ClientCAs holds the trusted issuers, ClientAuth set to tls.RequireAndVerifyClientCert turns enforcement on. Know that the pool by itself changes nothing.

for a middle

An interviewer at this level expects you to walk the five ClientAuthType values and say what each does when a certificate is absent or unverifiable, and to explain that the zero value is NoClientCert.

for a senior

Show that you know verification is authentication only. Explain that any certificate from that CA passes, that rejection happens at handshake time with no HTTP status, and where the evidence lands.

for a principal

Be able to argue what the internal CA's scope should be, since ClientCAs is effectively the boundary of who may call the service at all, and what the team gains over shared secrets.

## What mutual TLS changes in a Go server In ordinary TLS the client verifies the server and the server learns nothing about who is calling. Mutual TLS adds the second direction: during the handshake the server sends a CertificateRequest, the client answers with a certificate chain and a signature proving it holds the matching private key, and the server verifies that chain. In Go this is not a separate API — it is two fields on the `tls.Config` you hand to your server. ## The two fields **`ClientCAs *x509.CertPool`** is the set of certificate authorities the server is willing to accept client certificates from. It is the client-side mirror of `RootCAs`, and the two are independent: `RootCAs` is what a Go program uses when it dials out and verifies a *server*, `ClientCAs` is what a Go server uses when it verifies a *client*. A server usually builds this pool explicitly from its own internal CA rather than from the system roots, because trusting every public CA to authenticate your internal callers is meaningless. **`ClientAuth tls.ClientAuthType`** is the policy, and it has five values, in increasing strictness: - `tls.NoClientCert` — the zero value. No certificate is requested at all. - `tls.RequestClientCert` — one is requested, but the handshake succeeds whether or not the client sends it, and nothing is verified. - `tls.RequireAnyClientCert` — a certificate must be sent, but it is *not* verified against `ClientCAs`; a self-signed certificate is accepted. - `tls.VerifyClientCertIfGiven` — if a certificate is sent it must verify against `ClientCAs`; sending nothing is still fine. - `tls.RequireAndVerifyClientCert` — a certificate must be sent and must verify. This is the one that means what people mean by mTLS. Because `NoClientCert` is the zero value, forgetting `ClientAuth` is the classic mistake: the pool is populated, the code looks like mutual TLS, and the server never asks for anything. ## Where the config goes For an HTTP server, set `http.Server.TLSConfig` and call `ListenAndServeTLS(certFile, keyFile)` with the server's own certificate and key. For a hand-written RPC transport, wrap a plain listener with `tls.NewListener(ln, cfg)` or build the config into whatever accept loop you own. Either way the same `tls.Config` carries both the server identity (`Certificates` or `GetCertificate`) and the client-authentication policy. ## What verification actually checks When `RequireAndVerifyClientCert` is in force, the runtime builds a chain from the leaf the client sent up to a certificate in `ClientCAs`, checks signatures and validity periods along the way, and requires the leaf to be usable for client authentication (the extended key usage). If any of that fails, the handshake fails. What it does **not** check is identity. There is no client-side equivalent of hostname verification: the server does not compare the certificate's names against anything, because it has no expectation of who is dialling. Any certificate your CA has ever issued — including one issued to a different service, or to a laptop — satisfies `RequireAndVerifyClientCert`. That is why a verified chain is authentication, not authorisation, and why the handler still has to read the caller's name out of the peer certificate and decide whether that caller is allowed to make this call. ## What failure looks like A rejected client never reaches your handler. The failure happens during the handshake, so there is no request, no status code and no handler log line; the client sees a TLS error (typically a bad-certificate alert, or an error saying it was asked for a certificate it does not have), and the server writes a `http: TLS handshake error from ...` line to `http.Server.ErrorLog`. Teams migrating an internal service off shared secrets are often surprised by this: they expect a 401 they can page on, and instead the evidence is in handshake logs and client-side dial errors. ## Building the pool `x509.NewCertPool()` followed by `AppendCertsFromPEM` is the usual construction. `AppendCertsFromPEM` returns a bool, and it returns false when nothing parsed — checking it is worth the line, because an unreadable or empty CA file otherwise produces an empty pool that rejects every client with a confusing error.

  • Under RequireAndVerifyClientCert, what does the caller see when it presents no certificate?
    A handshake failure, not an HTTP response. The connection is torn down before any request is parsed, so no handler runs and no status code is produced. The dialing side gets a TLS error from its `Get` or `Dial` call, and the server logs a `http: TLS handshake error from ...` line through `http.Server.ErrorLog`. Monitoring that expects a 401 will see nothing at all.
  • Do you still need ClientCAs if you set ClientAuth to RequireAnyClientCert?
    No, and that is exactly why the mode is dangerous. `RequireAnyClientCert` demands that the client send a certificate but performs no chain verification, so `ClientCAs` is not consulted and a certificate anyone can generate in a second is accepted. It proves only that the peer holds some private key, not that a CA you trust vouched for it.
  • Is ClientCAs also used when the same process dials out to another service?
    No. `ClientCAs` is only consulted when this program acts as a TLS server verifying an incoming client certificate. When the same process dials out, the pool that matters is `RootCAs` on the outbound `tls.Config`, which verifies the remote server. Mixing the two up produces a server that trusts nobody or a client that trusts everybody.

saying these in an interview costs you the question

  • Thinks setting ClientCAs alone turns on client authentication
  • Confuses ClientCAs with RootCAs on the same config
  • Believes a chain that verifies proves the caller is authorised
  • Expects the handler to run and return 403 when no certificate is sent
  • Cannot name any ClientAuthType value other than the strict one