Example: HTTP Client Use Client Certificates
Some HTTPS APIs require the client to authenticate with a certificate during the TLS handshake. This is mutual TLS (mTLS).
In Go, the client certificate is configured through tls.Config.Certificates. Server certificate verification is a separate concern.
Configure a Client Certificate
A client certificate is usually provided as a certificate chain and a private key.
The essential configuration is:
Certificates contains the certificate chain the client presents to the server and the corresponding private key.
The server decides whether a client certificate is required. Configuring one does not cause the client to send it on every TLS connection regardless of the server's request.
The private key must match the public key in the client certificate. tls.LoadX509KeyPair checks the pair when it loads it.
Keep the private key out of source code, version-controlled configuration, logs, and error messages.
Do Not Disable Server Verification
Client authentication and server authentication are independent.
A client certificate proves the client's identity to the server. It does not tell the client whether it is connected to the correct server.
This is not required for mTLS:
InsecureSkipVerify disables normal verification of the server certificate and hostname. It should not be used as a workaround for client-certificate configuration.
For a normal public HTTPS service, leave it unset:
If the server uses a private CA, add that CA to the client's trust pool instead:
This example additionally imports:
RootCAs controls which certificate authorities the client trusts when verifying the server certificate.
It does not determine which CA the server uses to verify the client certificate.
Those are separate trust relationships.
Select a Client Certificate Dynamically
A client that always uses one certificate can put it directly in Certificates.
When certificate selection depends on the TLS server request, use GetClientCertificate.
When GetClientCertificate is set, Certificates is ignored.
The callback receives information from the server's CertificateRequest. AcceptableCAs contains the distinguished names of certificate authorities the server accepts for client authentication. It can be used to filter candidate certificates when several certificates are available.
It is not a universal certificate-selection mechanism. A server may send an empty CA list, and applications may have additional identity rules of their own.
Other fields in CertificateRequestInfo, such as SignatureSchemes, can also matter when selecting among certificates.
Certificate selection should be deterministic and cheap. Do not perform network I/O or slow external lookups during the TLS handshake. Load or refresh certificates outside the callback and make the callback return an already prepared certificate.
If no certificate should be presented, return an empty certificate:
The server may then reject the handshake if a client certificate is required.
Reuse the Client and Transport
Client certificates belong to TLS connections, while http.Transport manages those connections and their connection pools.
Create the client and transport once and reuse them:
A new Transport creates a new connection pool. Existing TLS connections cannot simply be moved to the new Transport.
This matters more with mTLS because establishing a new connection also requires another TLS handshake and another client-certificate authentication.
Client certificates authenticate a TLS connection, not an individual HTTP request.
HTTP/1.x requests sent over the same connection therefore use the same TLS identity. HTTP/2 makes this even more obvious: multiple concurrent requests can share one TLS connection through multiplexing.
If different tenants must never share a TLS connection, give each identity its own Transport and connection pool:
This is a connection-isolation requirement, not a general rule that every certificate needs its own http.Client.
A single Transport can select among certificates with GetClientCertificate, but certificate selection happens when the TLS connection is established. It cannot change the identity of an already established connection for a later request.
Build the Certificate Chain Correctly
The client certificate is often a chain rather than a single certificate.
A typical client certificate file contains:
The leaf certificate comes first, followed by any intermediate certificates needed by the server.
The root CA normally does not need to be sent. The server already has its trusted root in its trust store, so including the root in the client chain is normally redundant.
tls.LoadX509KeyPair preserves the certificate order when it loads the PEM data into tls.Certificate.Certificate.
For example:
Each element is the DER-encoded certificate from the corresponding PEM block.
If the server cannot build a trusted chain from the certificates the client sends, the TLS handshake can fail even when the leaf certificate itself is valid.
A valid leaf certificate and a complete certificate chain are different things.
When the Handshake Fails
A client-certificate failure occurs during TLS negotiation, before the HTTP request reaches the application.
The distinction is useful when debugging:
For mTLS-specific TLS errors, check:
- whether the server requested a client certificate
- whether the client certificate is valid and unexpired
- whether the private key matches the certificate
- whether the certificate chain contains the required intermediates
- whether the server trusts the issuing CA
- whether the certificate satisfies the server's client-authentication requirements
There is no HTTP status code to inspect when the TLS handshake itself fails.
A 401 or 403, by contrast, means the TLS connection was established successfully and the rejection happened after that point.