go 33 lines · 1 tab

mTLS client configuration with custom root CA pool

Leah Thompson Jan 2026
1 tab
package deps

import (
  "crypto/tls"
  "crypto/x509"
  "net/http"
  "os"
)

func NewMTLSClient(caPath, certPath, keyPath, serverName string) (*http.Client, error) {
  caPEM, err := os.ReadFile(caPath)
  if err != nil {
    return nil, err
  }
  pool := x509.NewCertPool()
  pool.AppendCertsFromPEM(caPEM)

  cert, err := tls.LoadX509KeyPair(certPath, keyPath)
  if err != nil {
    return nil, err
  }

  tr := &http.Transport{
    TLSClientConfig: &tls.Config{
      MinVersion:   tls.VersionTLS12,
      RootCAs:      pool,
      Certificates: []tls.Certificate{cert},
      ServerName:   serverName,
    },
  }

  return &http.Client{Transport: tr}, nil
}
1 file · go Explain with highlit

For internal service-to-service calls, mutual TLS is a pragmatic way to get strong identity without bespoke auth headers. The main pitfalls are certificate rotation and trust configuration. I build a x509.CertPool from a dedicated internal CA, load a client certificate/key pair, and set MinVersion to TLS1.2 (or newer) to avoid legacy negotiation. The tls.Config also sets ServerName so hostname verification happens correctly; skipping verification is a common anti-pattern. In production I pair this with short-lived certificates and automatic reload, but even the static version shown here is a solid baseline. When something breaks, the errors are actionable: either trust is wrong, cert is expired, or the peer identity doesn’t match.


Related snips

Share this code

Here's the card — post it anywhere.

mTLS client configuration with custom root CA pool — share card
Link copied