shadowsocks/shadowsocks-rust · error
HTTPS outbound proxy requires either `local-http-native-tls`
Error message
HTTPS outbound proxy requires either `local-http-native-tls` or `local-http-rustls` feature
What it means
HTTPS outbound proxy hops need a TLS backend; tls_unsupported is returned by tls_connect when the crate is built with neither `local-http-native-tls` nor `local-http-rustls`. The TLS layer is intentionally stubbed out so HTTPS hops cannot be established.
Source
Thrown at crates/shadowsocks-service/src/net/outbound/tls.rs:30
#[cfg(any(feature = "local-http-native-tls", feature = "local-http-rustls"))]
use std::{
pin::Pin,
task::{self, Poll},
};
#[cfg(any(feature = "local-http-native-tls", feature = "local-http-rustls"))]
use pin_project::pin_project;
#[cfg(any(feature = "local-http-native-tls", feature = "local-http-rustls"))]
use tokio::io::ReadBuf;
#[cfg(any(feature = "local-http-native-tls", feature = "local-http-rustls"))]
use super::stream::OutboundProxyStream;
/// Error helper for builds that disable both TLS backends.
#[inline]
pub fn tls_unsupported<T>() -> io::Result<T> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"HTTPS outbound proxy requires either `local-http-native-tls` or `local-http-rustls` feature",
))
}
/// TLS-wrapped outbound proxy stream.
///
/// The inner I/O object is a boxed [`OutboundProxyStream`]. Boxing is purely
/// structural here: TLS libraries bake the inner type into their own
/// generics, so without indirection every additional TLS hop in a chain
/// would explode the type. There is **no** dynamic dispatch involved
/// (the `Box<OutboundProxyStream>` is a sized concrete enum value, not
/// `Box<dyn Trait>`).
#[cfg(any(feature = "local-http-native-tls", feature = "local-http-rustls"))]
#[pin_project(project = OutboundTlsStreamProj)]
pub enum OutboundTlsStream {
#[cfg(all(feature = "local-http-native-tls", not(feature = "local-http-rustls")))]
NativeTls(#[pin] tokio_native_tls::TlsStream<Box<OutboundProxyStream>>, bool),View on GitHub (pinned to 8eb0f0a65b)
Solutions
- Rebuild with a TLS backend: --features local-http-rustls (or local-http-native-tls)
- Prefer the rustls backend for a pure-Rust build: cargo build --features local-http,local-http-rustls
- Downgrade the hop to plain HTTP or SOCKS5 if TLS is not actually required
Example fix
// before cargo build --features local-http // after cargo build --features local-http,local-http-rustls
Defensive patterns
Strategy: validation
Validate before calling
#[cfg(not(any(feature = "local-http-native-tls", feature = "local-http-rustls")))]
if matches!(hop.kind, OutboundProxyKind::Https{..}) {
return Err(anyhow!("https hop needs a TLS backend feature enabled"));
} Type guard
fn is_https_kind(k: &OutboundProxyKind) -> bool { matches!(k, OutboundProxyKind::Https{..}) } Try / catch
if let Err(e) = tls_connect(stream, domain).await {
if e.kind() == io::ErrorKind::Unsupported {
eprintln!("enable local-http-native-tls or local-http-rustls");
}
return Err(e.into());
} Prevention
- Always enable a TLS backend together with local-http
- Verify with `cargo tree -f '{p} {f}'` that a TLS feature is active
- Use rustls for portable pure-Rust builds
When it happens
Trigger: Config contains an `https` outbound proxy hop while the build enables `local-http` but neither TLS backend feature; negotiate_hop calls tls_connect which hits tls_unsupported.
Common situations: Enabling only `local-http` (plain HTTP) and expecting HTTPS to work; feature matrix confusion between native-tls and rustls backends; minimal CI builds missing the TLS feature.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- HTTP/HTTPS outbound proxy requires the `local-http` feature
- {method} don't know how to generate nonce
- invalid outbound_proxy
- resolve empty
- unexpected response from http://clients3.google.com/generate
AI-assisted analysis of shadowsocks/shadowsocks-rust@8eb0f0a65b (2026-09-09).
Data as JSON: /api/errors/3191344f48b3895c.
Report an issue: GitHub.