TLS with rustls
Terminate and originate TLS in Rust with rustls - pure-Rust TLS using aws-lc-rs or ring crypto backends.
Recipe
Quick-reference recipe card - copy-paste ready.
[dependencies]
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] }
rustls = "0.23"
tokio-rustls = "0.26"let client = reqwest::Client::builder()
.use_rustls_tls()
.build()?;When to reach for this: HTTPS clients and servers without OpenSSL dependency - common in Rust microservices and static binaries.
Working Example
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use std::sync::Arc;
fn load_certs(pem: &str) -> Vec<CertificateDer<'static>> {
rustls_pemfile::certs(&mut pem.as_bytes())
.map(|r| r.unwrap())
.collect()
}
fn load_key(pem: &str) -> PrivateKeyDer<'static> {
rustls_pemfile::private_key(&mut pem.as_bytes())
.next()
.unwrap()
.unwrap()
}
fn rustls_server_config(cert_pem: &str, key_pem: &str) -> Arc<rustls::ServerConfig> {
let certs = load_certs(cert_pem);
let key = load_key(key_pem);
let config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)
.unwrap();
Arc::new(config)
}What this demonstrates:
- PEM loading with
rustls-pemfile. ServerConfig::builder()with certificate chain and private key.Arcconfig shared across connections.- No OpenSSL system dependency.
Deep Dive
How It Works
- rustls implements TLS 1.2/1.3 without OpenSSL.
- reqwest/axum/hyper integrate via rustls connectors.
- WebPKI roots via
webpki-rootsor platform verifier crates.
Deployment Patterns
| Pattern | Where TLS runs |
|---|---|
| Edge termination | Load balancer (common) |
| Sidecar | Envoy/mTLS mesh |
| In-process | rustls on Axum (less common) |
Rust Notes
// mTLS client trusts custom CA
let mut roots = rustls::RootCertStore::empty();
roots.add(ca_cert).unwrap();
let config = rustls::ClientConfig::builder()
.with_root_certificates(roots)
.with_client_auth_cert(client_certs, client_key)?;Gotchas
- Certificate expiry - No auto-renew in app. Fix: cert-manager, Let's Encrypt, or load balancer managed certs.
- Disabled verification -
danger_accept_invalid_certsin prod. Fix: Proper root store and hostname verification. - TLS version mismatch - Old clients need TLS 1.2 enabled. Fix: Configure rustls policy for minimum version.
- Wrong SAN - Cert lacks service DNS name. Fix: Include all internal DNS names in CSR.
- Key in repo - Private key committed to git. Fix: Secrets manager mount at runtime.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| OpenSSL (native-tls) | Legacy enterprise CA tooling | Avoiding OpenSSL dep is goal |
| External termination | Kubernetes ingress | End-to-end encryption required inside VPC |
mesalink | OpenSSL compatibility layer | Greenfield rustls |
FAQs
reqwest default TLS?
Enable rustls-tls feature explicitly in Cargo.toml.
Axum HTTPS?
Often TLS at ingress; axum-server or hyper rustls for direct HTTPS.
Rotate certs?
Reload config on SIGHUP or restart pods with new secret volume.
Corporate proxy?
Custom root CA added to RootCertStore.
ALPN?
HTTP/2 negotiated via ALPN in rustls config for gRPC.
FIPS?
aws-lc-rs FIPS module where compliance requires.
Debug handshake?
RUSTLS_LOG=debug - never in prod with secrets.
Self-signed dev?
Local mkcert or dev-only custom root - never ship to prod.
Performance?
rustls competitive with OpenSSL; session resumption helps latency.
tonic TLS?
Server::builder().tls_config(...)? with identity from rustls.
Related
- HTTP Clients (reqwest) - HTTPS client
- gRPC with tonic - mTLS services
- Secrets Management - Key storage
- Networking Best Practices - TLS rules
- Cryptography (ring / RustCrypto) - Crypto primitives
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.