---
title: How to use rustls crate in Rust TLS
summary: Enable rustls in Rust by adding the rustls-tls feature to your reqwest dependency in Cargo.toml.
lang: en
slug: how-to-use-rustls-crate-in-rust-tls
published: 2026-04-17T12:05:42+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-use-rustls-crate-in-rust-tls/
---

# How to use rustls crate in Rust TLS



## When OpenSSL gets in the way

You are building a CLI tool that fetches configuration from a remote server. You drop `reqwest` into your `Cargo.toml`, write a few lines to get the JSON, and it works perfectly on your laptop. Then you try to build for a minimal Alpine container, or a platform that forbids dynamic linking, and the build fails. The default TLS backend needs OpenSSL headers and libraries that just aren't there. Or maybe you are auditing your dependencies and realize your app pulls in a massive C library just to handle HTTPS. You need a TLS implementation that lives entirely in Rust, compiles everywhere, and doesn't drag in C dependencies.

## What rustls actually is

`rustls` is a TLS library written entirely in Rust. Most HTTP clients in Rust default to `native-tls`, which wraps the operating system's TLS library. On Linux, that is usually OpenSSL. OpenSSL is written in C. It is battle-tested, but it requires C headers to compile, it links dynamically by default, and it introduces a dependency on a codebase you didn't write and can't audit as easily.

`rustls` replaces that C layer. It implements the TLS protocol from scratch in safe Rust. No FFI. No C dependencies. It compiles to any target that Rust supports, including WebAssembly, and it enforces memory safety guarantees that C libraries simply cannot provide. The code is reviewed, the types are checked, and the borrow checker ensures no use-after-free bugs can slip into the handshake logic.

Rust doesn't trust C. `rustls` proves you don't have to either.

## Switching reqwest to rustls

The most common way to use `rustls` is through `reqwest`. `reqwest` supports multiple TLS backends via feature flags. The default backend is `native-tls`. Switching to `rustls` requires two steps: disabling the default features and enabling the `rustls-tls` feature.

```toml
[dependencies]
# Disable default features to remove native-tls.
# Enable rustls-tls to use the pure Rust backend.
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
```

```rust
/// Fetches data using reqwest with the rustls backend.
async fn fetch_data() -> Result<(), Box<dyn std::error::Error>> {
    // Create a client builder.
    // The rustls feature is active, so this client uses rustls automatically.
    let client = reqwest::Client::new();

    // Perform the request.
    // rustls handles certificate verification and encryption.
    let response = client.get("https://httpbin.org/get").send().await?;
    let text = response.text().await?;

    println!("Response: {}", text);
    Ok(())
}
```

The key here is `default-features = false`. `reqwest` enables `native-tls` by default. If you just add `rustls-tls` without disabling defaults, you end up with both backends compiled into your binary. That bloats your build and wastes space. The convention is to disable defaults and pick exactly what you need.

Disable defaults. Pick your backend. Keep your binary lean.

## How the handshake works

When you compile, `cargo` fetches `rustls` and its dependencies. Since everything is Rust, the compiler checks types and memory safety. No `cc` crate invoking `gcc` or `clang`. No missing `openssl-dev` packages. The build is reproducible across platforms.

At runtime, when you call `client.get()`, `reqwest` delegates the connection setup to `rustls`. `rustls` performs the TLS handshake, verifies the server's certificate against the trust store, and establishes the encrypted channel. If the certificate is expired or the hostname doesn't match, `rustls` rejects the connection. You get an error, not a silent downgrade.

`rustls` prefers TLS 1.3. It falls back to TLS 1.2 if the server doesn't support 1.3. It disables TLS 1.0 and 1.1 entirely. It also disables weak ciphers by default. You don't have to configure this. The defaults are secure. OpenSSL often defaults to allowing older protocols for compatibility, which introduces risk. `rustls` forces you to be modern.

Trust the defaults. They are safer than you think.

## Handling certificate roots

`rustls` does not bundle root certificates by default. This keeps the crate size down and avoids shipping a static list of certs that might become outdated. Instead, `rustls` relies on the caller to provide roots. `reqwest` handles this via features.

In a standard desktop environment, you can use `rustls-native-certs`. This feature tells `rustls` to load the system's certificate store. On Linux, it reads from `/etc/ssl/certs`. On macOS, it uses the Keychain. On Windows, it uses the Windows certificate store.

```toml
[dependencies]
# Use native-certs to load system roots.
# Good for desktop apps where the OS manages certificates.
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "rustls-native-certs"] }
```

In minimal environments like Alpine containers or WebAssembly, the system might not have a root certificate store. You need `webpki-roots`. This feature bundles a curated set of root certificates from Mozilla. It ensures your app can verify certificates even in an empty container.

```toml
[dependencies]
# Use webpki-roots for environments without system certs.
# Essential for minimal containers and reproducible builds.
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "webpki-roots"] }
```

The convention is clear. Use `webpki-roots` for containers and reproducible builds. Use `rustls-native-certs` for desktop applications that should respect the user's system configuration. If you see a certificate verification error in a clean container, check your roots, not your code.

Check your roots before you blame the protocol.

## Customizing rustls behavior

`reqwest` exposes enough configuration to cover most use cases. You can set timeouts, disable verification for testing, or add client certificates.

```rust
use reqwest::Client;
use std::time::Duration;

/// Builds a client with custom timeouts and rustls.
fn build_configured_client() -> Client {
    Client::builder()
        // Set a timeout for the entire request.
        .timeout(Duration::from_secs(10))
        // Build the client.
        // rustls is used because of the Cargo.toml feature flag.
        .build()
        .expect("Failed to build client")
}
```

For local development, you might encounter self-signed certificates. `reqwest` allows you to disable verification, but this is dangerous.

```rust
/// Builds a client that skips certificate verification.
/// WARNING: Only use this for local development or testing.
/// Never use this in production.
fn build_insecure_client() -> Client {
    Client::builder()
        // Disable certificate verification.
        // This makes the connection vulnerable to MITM attacks.
        .danger_accept_invalid_certs(true)
        .build()
        .expect("Client build failed")
}
```

If you disable verification, you own the risk. The compiler won't stop you, but the network will expose you. Use this only when you control both ends of the connection and understand the implications.

If you disable verification, you own the risk.

## Pitfalls to avoid

The biggest trap is forgetting `default-features = false`. You add `rustls-tls`, compile, and wonder why your binary is larger than expected. You have both OpenSSL and `rustls` linked. The compiler won't stop you. It just includes both. Check your `Cargo.toml` and ensure defaults are off.

Another trap is running in a minimal environment without roots. `rustls` cannot verify certificates if it has no roots to check against. If you deploy to Alpine and forget `webpki-roots`, every HTTPS request fails with a certificate error. Add the feature or load roots manually.

Performance is rarely an issue. `rustls` is fast. It uses efficient algorithms and avoids unnecessary allocations. In benchmarks, it often matches or exceeds OpenSSL for TLS 1.3. If you see performance problems, profile your application. The TLS layer is rarely the bottleneck.

Bloat is silent. Check your features.

## Choosing your TLS backend

Use `rustls` when you need a pure Rust stack with no C dependencies. Use `rustls` when you are targeting WebAssembly or exotic architectures where OpenSSL isn't available. Use `rustls` when you want to audit your TLS implementation or enforce memory safety across the entire stack. Use `rustls` when you are building for minimal containers and want reproducible builds without system packages.

Use `native-tls` when you are building a desktop application that should leverage the OS's native certificate store and TLS preferences for maximum compatibility with local system settings. Use `native-tls` when your deployment environment already has OpenSSL installed and you want to minimize binary size by linking against the system library rather than bundling TLS code. Use `native-tls` when you need to support legacy protocols that `rustls` has dropped, though you should question why you need legacy protocols.

Pick the backend that matches your deployment reality, not your comfort zone.

## Where to go next

- [How to Use TLS/SSL in Rust (rustls, native-tls)](/en/how-to-use-tlsssl-in-rust-rustls-native-tls/)
- [How to Use the rustls Crate for TLS](/en/how-to-use-the-rustls-crate-for-tls/)