TLS / mTLS Configuration¶
httptape supports TLS configuration for both outbound connections (httptape to upstream) and inbound connections (clients to httptape).
Inbound TLS (listener)¶
httptape can listen on HTTPS instead of plain HTTP. This is useful when clients require TLS (e.g., mobile SDKs that reject plain HTTP, or browser-based dev tools with mixed-content restrictions).
Two modes are available:
| Mode | Use case | Flags |
|---|---|---|
| Self-signed | Quick local dev/CI -- no cert files needed | --tls-listener-auto |
| Explicit cert | Bring your own PEM cert/key pair | --tls-listener-cert + --tls-listener-key |
These flags are available on the serve, record, and proxy commands. The existing --tls-* flags (outbound) are unrelated and continue to work.
Self-signed (auto-generate)¶
At startup, httptape generates an ECDSA P-256 self-signed certificate (24h validity, 1h clock-skew window) covering the default SANs localhost, 127.0.0.1, and ::1. The SHA-256 fingerprint is printed to stderr.
To customize the SANs:
httptape serve \
--fixtures ./fixtures \
--tls-listener-auto \
--tls-listener-san "myhost.local,10.0.0.1"
For development and tests only
Self-signed certificates are not trusted by default. Clients must either skip verification or programmatically trust the certificate. Never use self-signed certificates in production.
Explicit certificate¶
httptape serve \
--fixtures ./fixtures \
--tls-listener-cert /path/to/server.crt \
--tls-listener-key /path/to/server.key
Mutual exclusion¶
--tls-listener-auto is mutually exclusive with --tls-listener-cert/--tls-listener-key. Using both produces a usage error. --tls-listener-san requires --tls-listener-auto.
Go library usage (GenerateSelfSignedCert)¶
The GenerateSelfSignedCert function generates a self-signed certificate for programmatic use in tests:
import "github.com/httptape/httptape"
sc, err := httptape.GenerateSelfSignedCert("localhost", "127.0.0.1")
if err != nil {
log.Fatal(err)
}
// Use sc.TLSCertificate in a tls.Config for the listener.
srv := &http.Server{
Handler: myHandler,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{sc.TLSCertificate},
},
}
// Trust the cert in test clients via sc.CertPEM.
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(sc.CertPEM)
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: pool},
},
}
With no arguments, GenerateSelfSignedCert() covers localhost, 127.0.0.1, and ::1.
Outbound TLS (upstream)¶
httptape supports custom TLS configuration for outbound connections (httptape to upstream). This enables recording and proxying through backends that use self-signed certificates, internal CAs, or mutual TLS (mTLS).
Four levels of outbound TLS¶
| Level | Use case | Configuration |
|---|---|---|
| Basic TLS | Upstream uses HTTPS with a publicly trusted certificate | None -- works out of the box |
| Custom CA | Upstream uses a self-signed or internal CA certificate | --tls-ca |
| mTLS | Upstream requires a client certificate | --tls-cert + --tls-key (+ optional --tls-ca) |
| Skip verify | Dev shortcut when certs are broken or self-signed | --tls-insecure |
CLI usage¶
Custom CA¶
httptape record \
--upstream https://internal-api.corp:8443 \
--fixtures ./fixtures \
--tls-ca /path/to/internal-ca.pem
Mutual TLS (mTLS)¶
httptape proxy \
--upstream https://secure-api.corp:8443 \
--fixtures ./fixtures \
--tls-cert /path/to/client.crt \
--tls-key /path/to/client.key \
--tls-ca /path/to/internal-ca.pem
Skip TLS verification (dev only)¶
Warning
--tls-insecure disables all certificate verification. Never use this in production. A warning is printed to stderr when this flag is active.
Go library usage¶
BuildTLSConfig helper¶
The BuildTLSConfig function converts file paths into a *tls.Config:
import "github.com/httptape/httptape"
// Custom CA only
tlsCfg, err := httptape.BuildTLSConfig("", "", "/path/to/ca.pem", false)
// mTLS with custom CA
tlsCfg, err := httptape.BuildTLSConfig(
"/path/to/client.crt",
"/path/to/client.key",
"/path/to/ca.pem",
false,
)
// Skip verification (dev only)
tlsCfg, err := httptape.BuildTLSConfig("", "", "", true)
When all arguments are zero-valued, BuildTLSConfig returns nil, nil (use Go defaults).
Recorder with TLS¶
tlsCfg, err := httptape.BuildTLSConfig("", "", "ca.pem", false)
if err != nil {
log.Fatal(err)
}
store, _ := httptape.NewFileStore(httptape.WithDirectory("fixtures"))
rec := httptape.NewRecorder(store, httptape.WithRecorderTLSConfig(tlsCfg))
defer rec.Close()
client := &http.Client{Transport: rec}
resp, err := client.Get("https://internal-api.corp:8443/v1/data")
Proxy with TLS¶
tlsCfg, err := httptape.BuildTLSConfig("client.crt", "client.key", "ca.pem", false)
if err != nil {
log.Fatal(err)
}
l1 := httptape.NewMemoryStore()
l2, _ := httptape.NewFileStore(httptape.WithDirectory("fixtures"))
proxy := httptape.NewProxy(l1, l2, httptape.WithProxyTLSConfig(tlsCfg))
client := &http.Client{Transport: proxy}
Docker¶
When running httptape in Docker, mount your certificate files into the container:
docker run -v /host/certs:/certs:ro \
httptape record \
--upstream https://backend:8443 \
--fixtures /data/fixtures \
--tls-cert /certs/client.crt \
--tls-key /certs/client.key \
--tls-ca /certs/ca.pem
Troubleshooting¶
"x509: certificate signed by unknown authority"¶
The upstream certificate is not trusted. Provide the CA certificate with --tls-ca, or use --tls-insecure as a temporary workaround.
"x509: certificate has expired or is not yet valid"¶
The upstream (or client) certificate is outside its validity window. Check the NotBefore and NotAfter fields with:
"tls: private key does not match public key"¶
The --tls-cert and --tls-key files do not form a valid pair. Verify with:
openssl x509 -in client.crt -noout -modulus | md5
openssl ec -in client.key -noout -modulus 2>/dev/null | md5
# (Use openssl rsa for RSA keys)
"--tls-cert requires --tls-key" (or vice versa)¶
Client certificate and key must be provided together. Supply both or neither.