rustls: HTTPS for Axum & Reqwest in Rust Guide

Max WellsMax WellsFounder of Rustify

TL;DR: rustls is a modern TLS 1.2/1.3 implementation written in pure Rust; no OpenSSL dependency, no C code, memory-safe by construction. It is the recommended way to add HTTPS to Rust servers and clients. Use axum-server with rustls for HTTPS in Axum, tokio-rustls for direct TLS on TCP streams, and reqwest with the rustls-tls feature for HTTPS client requests. Load certificates from files or use rcgen to generate self-signed certs for development.


What Is rustls?

rustls is a TLS library that implements the TLS 1.2 and 1.3 protocols in safe Rust, replacing the need for OpenSSL or native-tls in most Rust applications.

Why use rustls over OpenSSL:

  • Memory safe: no C code → no buffer overflows, use-after-free
  • No system dependency: statically linked; no libssl required on deployment target
  • Modern defaults: TLS 1.3 by default, strong cipher suites only
  • Audited: regularly security-audited by ISRG and others

How Do You Add HTTPS to an Axum Server?

[dependencies]
axum = "0.8"
axum-server = { version = "0.7", features = ["tls-rustls"] }
tokio = { version = "1", features = ["full"] }
use axum::{Router, routing::get};
use axum_server::tls_rustls::RustlsConfig;
 
#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(|| async { "Hello, HTTPS!" }));
 
    // Load TLS certificate and private key
    let config = RustlsConfig::from_pem_file(
        "certs/cert.pem",
        "certs/key.pem",
    )
    .await
    .unwrap();
 
    let addr = std::net::SocketAddr::from(([0, 0, 0, 0], 443));
    println!("Listening on https://{addr}");
 
    axum_server::bind_rustls(addr, config)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

How Do You Make HTTPS Requests With reqwest + rustls?

[dependencies]
reqwest = { version = "0.12", features = ["rustls-tls"], default-features = false }
tokio = { version = "1", features = ["full"] }
use reqwest::Client;
 
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    // reqwest with rustls-tls uses rustls instead of native-tls
    let client = Client::builder()
        .use_rustls_tls()
        .build()?;
 
    let response = client
        .get("https://api.example.com/data")
        .header("Authorization", "Bearer token123")
        .send()
        .await?;
 
    println!("Status: {}", response.status());
    let body = response.text().await?;
    println!("Body: {body}");
 
    Ok(())
}

How Do You Use tokio-rustls for Custom TLS Streams?

For direct TCP with TLS; lower level than axum-server.

[dependencies]
tokio-rustls = "0.26"
rustls = "0.23"
use tokio_rustls::TlsAcceptor;
use rustls::ServerConfig;
use std::sync::Arc;
 
async fn create_tls_acceptor(cert_path: &str, key_path: &str) -> TlsAcceptor {
    let certs = load_certs(cert_path);
    let key = load_private_key(key_path);
 
    let config = ServerConfig::builder()
        .with_no_client_auth()
        .with_single_cert(certs, key)
        .unwrap();
 
    TlsAcceptor::from(Arc::new(config))
}
 
// Accept TLS connections
let acceptor = create_tls_acceptor("cert.pem", "key.pem").await;
let listener = tokio::net::TcpListener::bind("0.0.0.0:443").await.unwrap();
 
loop {
    let (stream, _) = listener.accept().await.unwrap();
    let tls_stream = acceptor.accept(stream).await.unwrap();
    // handle tls_stream as a normal AsyncRead + AsyncWrite
}

How Do You Generate Development Certificates?

[dev-dependencies]
rcgen = "0.13"
use rcgen::generate_simple_self_signed;
 
fn generate_dev_certs() {
    let subject_alt_names = vec!["localhost".to_string(), "127.0.0.1".to_string()];
    let cert = generate_simple_self_signed(subject_alt_names).unwrap();
 
    std::fs::write("certs/cert.pem", cert.cert.pem()).unwrap();
    std::fs::write("certs/key.pem", cert.key_pair.serialize_pem()).unwrap();
}

For production, use Let's Encrypt (via instant-acme crate) or load certificates issued by your CA.


Frequently Asked Questions

Prefer rustls for new projects; it is memory-safe, has no system dependencies, and is easier to deploy (no OpenSSL version mismatch). Use native-tls only when you need to trust the OS certificate store explicitly or have a legacy system requirement.

No; reqwest's default features include native-tls. Switch to rustls-tls by disabling default features:

reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] }

No; rustls only supports TLS 1.2 and 1.3 by design. TLS 1.0 and 1.1 are deprecated and insecure. If you need legacy TLS support, use openssl directly (not recommended).

let client = reqwest::Client::builder()
    .danger_accept_invalid_certs(true)  // dev only!
    .build()?;

Never use this in production.


Sources


  • Axum: axum-server uses rustls for HTTPS
  • Tokio: tokio-rustls integrates TLS with async I/O
  • Reqwest: HTTP client with rustls-tls feature
  • Argon2: Rust security stacks often combine rustls for transport security with Argon2 for password hashing
  • Tonic: Production gRPC services commonly pair Tonic with rustls for TLS

Keep Reading

Ready to Land a $120k+ Rust Job in the US or Europe?