Effective Routing and Client IP Preservation in Nginx Stream Module Based on SNI
In modern network architectures, it’s common to route TCP trafficโsuch as HTTPSโbased on the Server Name Indication (SNI) provided during the TLS handshake. Nginx’s stream module offers powerful capabilities to handle such scenarios, but it also presents certain limitations, especially when it comes to conditionally enabling features like proxy_protocol
based on dynamic variables.
This article discusses how to route TCP traffic based on SNI in Nginx, ensuring that client IP addresses are accurately logged, particularly when forwarding traffic to a local server with proxy_protocol
enabled only for certain conditions.
The Challenge
Suppose you want to:
- Route incoming SNI-based HTTPS traffic to different upstreams.
- Forward incorrect or unexpected SNI traffic to a local server at port 4443.
- Preserve the original client IP address when forwarding traffic to this local server, which requires enabling
proxy_protocol
. - Enable
proxy_protocol
only for traffic with incorrect SNI, without affecting traffic with the correct SNI.
The main complication is that in Nginx’s stream context, proxy_protocol
cannot be dynamically set using variables โ it is a static directive that must be enabled at configuration load time.
The Existing Approach
Here’s an outline of the current configuration attempt:
“`nginx
stream {
# Map SNI to backend destinations
map $ssl_preread_server_name $backend {
“” local_website;
correct_website.com upstream_website;
default local_website;
}
# Determine if proxy_protocol should be enabled
map $ssl_preread_server_name $is_proxy_on {
"" on;
correct_website.com off;
default on;
}
upstream upstream_website {
server upstream_website.com;
}
upstream local_website {
server 127.0.0.1:4443;
}
server {
listen 443;
ssl_preread on;
proxy_protocol $is_proxy_on; # Attempt to conditionally enable proxy_protocol
proxy_pass $backend;
}
}
“`
The core issue here: proxy_protocol
cannot be controlled via variables. It requires a static proxy_protocol
directive at configuration parse time, not dynamically per request.
Potential Solutions
While Apache and some