This directory contains practical examples of using mTLS certificates generated by the mtls tool.
- node-server - Node.js HTTPS server with mTLS
- node-client - Node.js HTTPS client with mTLS testing
- python-server - Python HTTPS server using built-in modules
- python-client - Python HTTPS client using urllib
- php-server - PHP server with mTLS (Apache/Nginx recommended for production)
- php-client - PHP client using stream contexts
- rust-server - High-performance Rust server with Axum and Rustls
- rust-client - Rust client with Reqwest and Rustls
- caddy - Caddy web server with mTLS configuration
From the project root:
# Create a Root CA
./mtls ca create --batch \
--cn "Example CA" \
--org "Example Organization" \
--country "KR" \
--key-type rsa4096 \
--years 10
# Create a server certificate
./mtls cert create --batch \
--ca ./certs/ca \
--cn "localhost" \
--org "Example Server" \
--dns "localhost,127.0.0.1" \
--ip "127.0.0.1" \
--key-type rsa2048 \
--years 5cd examples/go-server
go run main.gocd examples/node-server
npm install # First time only
npm startcd examples/python-server
python3 server.pycd examples/php-server
./run.shcd examples/rust-server
cargo run --releasecd examples/caddy
./run.shChoose a client in any language:
# Go client
cd examples/go-client
go run main.go
# Node.js client
cd examples/node-client
npm install # First time only
npm start
# Python client
cd examples/python-client
python3 client.py
# PHP client
cd examples/php-client
./run.sh
# Rust client
cd examples/rust-client
cargo run --release| Language | Server Framework | TLS Library | Async | Dependencies | Performance | Best For |
|---|---|---|---|---|---|---|
| Go | net/http | crypto/tls | ✅ Goroutines | Zero (stdlib) | ⭐⭐⭐⭐⭐ | Microservices, CLI tools |
| Node.js | https | OpenSSL | ✅ Event loop | Zero (built-in) | ⭐⭐⭐⭐ | Web APIs, real-time |
| Python | http.server | ssl | ❌ Sync | Zero (stdlib) | ⭐⭐⭐ | Scripts, prototyping |
| PHP | Built-in/Apache | OpenSSL | ❌ Sync | Zero (ext-openssl) | ⭐⭐ | Legacy systems |
| Rust | Axum | Rustls | ✅ Tokio | 9 crates | ⭐⭐⭐⭐⭐ | High-performance, safety |
| Caddy | Caddy | Go crypto/tls | ✅ Built-in | Config only | ⭐⭐⭐⭐⭐ | Reverse proxy, edge |
Choose Go if you need microservices with excellent concurrency and zero dependencies.
Choose Node.js if you're building web APIs and your team knows JavaScript/TypeScript.
Choose Python if you're prototyping, writing scripts, or need data science integration.
Choose PHP if you have legacy PHP applications or use Apache/Nginx with PHP-FPM.
Choose Rust if you need maximum performance, memory safety, and systems-level programming.
Choose Caddy if you need a reverse proxy, automatic HTTPS, or configuration simplicity
┌─────────────┐ ┌─────────────┐
│ │ mTLS Connection │ │
│ Go Client │◄──────────────────►│ Server │
│ │ (Mutual Auth) │ (Go/Caddy) │
└─────────────┘ └─────────────┘
│ │
│ ┌────────────────┐ │
└─┤ Client Cert │ │
│ + Private Key │ │
└────────────────┘ │
│
┌─────────▼──────┐
│ Server Cert │
│ + Private Key │
└────────────────┘
│
┌─────────────────▼─────────────┐
│ Trusted CA Cert │
│ (for verifying both sides) │
└───────────────────────────────┘
Both client and server have valid certificates signed by the same CA.
# Terminal 1
cd examples/go-server && go run main.go
# Terminal 2
cd examples/go-client && go run main.go✅ Expected: All tests pass
Modify the client to not send a certificate.
curl https://localhost:8443❌ Expected: Connection refused (no client certificate)
Try using a certificate from a different CA.
❌ Expected: TLS handshake fails (certificate verification error)
curl --cert ../../certs/servers/localhost/server-cert.pem \
--key ../../certs/servers/localhost/server-key.pem \
--cacert ../../certs/ca/ca-cert.pem \
https://localhost:8443✅ Expected: Success response from server
- Local development with mTLS
- Integration testing
- API testing with mutual authentication
- Service-to-service authentication
- Zero-trust networking
- Internal API security
- Device authentication
- Secure communication with edge devices
- Certificate-based device identity
- Internal tools requiring strong authentication
- Admin panels with certificate-based access
- Secure API gateways
- Certificate Rotation: Rotate server certificates regularly (1-2 years)
- CA Security: Keep the CA private key extremely secure
- Certificate Revocation: Implement CRL or OCSP for production
- Monitoring: Log certificate expiration dates
- Access Control: Limit who can generate certificates
- ✅ Strong Authentication: Both client and server verify each other
- ✅ Encrypted Communication: All traffic is TLS-encrypted
- ✅ Non-repudiation: Certificate-based identity
- ✅ Zero-Trust: No implicit trust, always verify
- ❌ Authorization: You still need to implement access control
- ❌ Rate Limiting: Implement separately if needed
- ❌ DDoS Protection: Additional measures required
- ❌ Application Security: Secure coding practices still necessary
# Find and kill the process using port 8443
lsof -ti:8443 | xargs kill -9# Check certificate validity
openssl x509 -in certs/servers/localhost/server-cert.pem -noout -datesMake sure you're running commands from the correct directory:
# For go-server and go-client, run from their respective directories
cd examples/go-server # or go-client
go run main.go
# Certificate paths are relative: ../../certs/...Common causes:
- Certificate and CA mismatch
- Expired certificates
- Incorrect certificate paths
- TLS version mismatch
Enable debug logging:
# For Go programs
GODEBUG=x509roots=1 go run main.goSee go-client/main.go for examples of:
- Loading certificates
- Configuring TLS
- Custom verification logic
Generate certificates for different purposes:
# Admin certificate
./mtls cert create --batch --ca ./certs/ca --cn "admin.local"
# Service certificate
./mtls cert create --batch --ca ./certs/ca --cn "service.local"
# IoT device certificate
./mtls cert create --batch --ca ./certs/ca --cn "device-001"In your server code:
func authorizeAdmin(r *http.Request) bool {
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
return false
}
cert := r.TLS.PeerCertificates[0]
// Check if certificate has admin role
for _, org := range cert.Subject.Organization {
if org == "Admin" {
return true
}
}
return false
}Feel free to add more examples! Some ideas:
- gRPC with mTLS
- WebSocket with mTLS
- Docker container with mTLS
- Kubernetes ingress with mTLS
- nginx configuration
- Python client example