Introduction

When Reqwest makes an HTTPS request, the TLS handshake can fail if the client and server cannot agree on a TLS version or cipher suite. This commonly happens when connecting to older servers that only support TLS 1.0/1.1, when the system's root certificates are outdated, or when the rustls backend does not support the server's certificate algorithm. The error manifests as a connection failure that is difficult to diagnose without understanding the TLS negotiation process.

Symptoms

  • error sending request: reqwest::Error { kind: Request, source: hyper::Error(Connect, ConnectError(Custom { kind: Other, error: Custom { kind: InvalidData, error: InvalidCertificate(Expired) } }))}
  • handshake failure or protocol version errors
  • Works with curl but fails with Reqwest
  • Works on one machine but not another (different TLS backend)
  • native-tls works but rustls fails or vice versa

Error output: `` Error: reqwest::Error { kind: Request, url: Url { scheme: "https", host: "api.example.com" }, source: hyper::Error(Connect, ConnectError { kind: Other, error: Custom { kind: InvalidData, error: InvalidCertificate(UnknownIssuer) } }) }

Common Causes

  • Server uses self-signed or internally-signed certificate
  • System root certificates outdated or missing
  • rustls does not support the server's certificate algorithm
  • Server only supports deprecated TLS versions (1.0, 1.1)
  • Corporate proxy intercepting TLS connections

Step-by-Step Fix

  1. 1.Configure minimum TLS version:
  2. 2.```rust
  3. 3.use reqwest::Client;

// With rustls (default backend) let client = Client::builder() .min_tls_version(reqwest::tls::Version::TLS_1_2) .build()?;

// Allow older TLS versions if connecting to legacy servers let client = Client::builder() .min_tls_version(reqwest::tls::Version::TLS_1_0) .danger_accept_invalid_certs(true) // ONLY for testing! .build()?; ```

  1. 1.Switch between rustls and native-tls backends:
  2. 2.```toml
  3. 3.# Cargo.toml - use rustls (default)
  4. 4.[dependencies]
  5. 5.reqwest = { version = "0.12", features = ["json"] }

# Use native-tls (uses system TLS library) [dependencies] reqwest = { version = "0.12", features = ["json", "native-tls"] }

# Use rustls with webpki-roots (bundled certificates) [dependencies] reqwest = { version = "0.12", features = ["json", "rustls-tls-webpki-roots"] } ```

  1. 1.Add custom root certificate for internal APIs:
  2. 2.```rust
  3. 3.use reqwest::Client;
  4. 4.use std::fs;

let client = Client::builder() .add_root_certificate( reqwest::Certificate::from_pem( &fs::read("/path/to/internal-ca.pem")? )? ) .build()?;

let response = client .get("https://internal-api.example.com/health") .send() .await?; ```

  1. 1.Debug TLS connection issues with curl comparison:
  2. 2.```bash
  3. 3.# Test with curl verbose TLS info
  4. 4.curl -v --tlsv1.2 https://api.example.com/health 2>&1 | grep -E "TLS|SSL|certificate"

# Check which TLS version the server supports openssl s_client -connect api.example.com:443 -tls1_2 </dev/null 2>&1 | grep "Protocol|Cipher"

# Check certificate chain openssl s_client -connect api.example.com:443 -showcerts </dev/null 2>&1 | openssl x509 -noout -issuer -subject -dates ```

  1. 1.Handle certificate errors gracefully:
  2. 2.```rust
  3. 3.async fn fetch_with_tls_fallback(url: &str) -> Result<String, Box<dyn std::error::Error>> {
  4. 4.// Try with strict TLS first
  5. 5.let client = Client::builder()
  6. 6..min_tls_version(reqwest::tls::Version::TLS_1_2)
  7. 7..build()?;

match client.get(url).send().await { Ok(response) => { let body = response.text().await?; Ok(body) } Err(e) if e.is_request() => { tracing::warn!( "Strict TLS request failed for {}, trying with relaxed settings", url );

// Fallback with more permissive settings let relaxed_client = Client::builder() .min_tls_version(reqwest::tls::Version::TLS_1_0) .danger_accept_invalid_certs(true) .build()?;

let response = relaxed_client.get(url).send().await?; Ok(response.text().await?) } Err(e) => Err(e.into()), } } ```

Prevention

  • Keep system root certificates updated (ca-certificates package on Linux)
  • Use native-tls feature for maximum compatibility with diverse servers
  • Pin reqwest version to avoid TLS backend changes in upgrades
  • Add TLS version requirements to API documentation
  • Test against production TLS configuration in CI
  • Monitor TLS handshake failure rate in production metrics