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 failureorprotocol versionerrors- Works with curl but fails with Reqwest
- Works on one machine but not another (different TLS backend)
native-tlsworks butrustlsfails 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
rustlsdoes 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.Configure minimum TLS version:
- 2.```rust
- 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.Switch between rustls and native-tls backends:
- 2.```toml
- 3.# Cargo.toml - use rustls (default)
- 4.[dependencies]
- 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.Add custom root certificate for internal APIs:
- 2.```rust
- 3.use reqwest::Client;
- 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.Debug TLS connection issues with curl comparison:
- 2.```bash
- 3.# Test with curl verbose TLS info
- 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.Handle certificate errors gracefully:
- 2.```rust
- 3.async fn fetch_with_tls_fallback(url: &str) -> Result<String, Box<dyn std::error::Error>> {
- 4.// Try with strict TLS first
- 5.let client = Client::builder()
- 6..min_tls_version(reqwest::tls::Version::TLS_1_2)
- 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-certificatespackage on Linux) - Use
native-tlsfeature for maximum compatibility with diverse servers - Pin
reqwestversion 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