How to proxy multiple tcp streams on one port with nginx
Yes, if nginx was compiled with --with-stream
. https://www.nginx.com/resources/admin-guide/tcp-load-balancing/
Here is one of the examples with the same port:
upstream stream_backend {
hash $remote_addr;
server backend1.example.com:12345;
server backend2.example.com:12345;
server backend3.example.com:12346;
}
Second update:
You obviously can’t do anything like this:
stream {
upstream db1.example.com {
server db1.example.com:3306;
#server_name db1.example.com; "server_name" directive is not allowed here
}
upstream db2.example.com {
server db2.example.com:3306;
}
server {
listen 3306;
proxy_pass db1.example.com;
}
#duplicate "3306" address and port pair
#server { listen 3306; proxy_pass db2.example.com; }
}
Because the nginx proxy for upstream db1.example.com
is competing with db2.example.com
for packets on port 3306. So you have to have the proxy for db1.example.com listen
ing on another port
than the proxy for db2.example.com
. Otherwise nginx wouldn’t know how to route packets to and from the two upstreams. Apologies for misunderstanding your original post. server_name
isn't allowed in stream definitions because, unlike http headers, there is no additional metadata in the tcp/udp packet that identifies which DNS was used to address the packet over to nginx.
It is possible to direct connections based on the SNI header using the nginx ssl preread module.
This depends on the client specifying an SNI header when opening the connection.
In the example below, nginx listens for connections on port 443. A connection to presence.myglance.org:443 is forwarded to port 4443 (that's an http server, not shown). A connection to presence-s.myglance.org:443 is forwareded to a stream server listening on port 5502, which terminates the ssl connection and forwards to port 6502.
stream {
############################################################
### logging
log_format log_stream '$remote_addr [$time_local] $protocol [$ssl_preread_server_name] [$ssl_preread_alpn_protocols] [$internalport] '
'$status $bytes_sent $bytes_received $session_time';
error_log /home/Logs/error.log debug;
access_log /home/Logs/access.log log_stream;
### https://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html
#########################################################################
# Connections on 443 could be raw socket or https / wss
# Distinguish between the two based on the SNI (preread server name)
# Server names with -s are raw socket connections
# Server names without -s are https or wss connections
map $ssl_preread_server_name $internalport {
presence-s.myglance.org 5502;
presence.myglance.org 4443;
default glance-no-upstream-instance;
}
# Note this does not terminate the ssl connection. It just reads the SNI
# header and forwards to the appropriate port
server {
listen 443;
ssl_preread on;
proxy_connect_timeout 20s; # max time to connect to pserver
proxy_timeout 30s; # max time between successive reads or writes
proxy_pass 127.0.0.1:$internalport;
}
server {
listen 5502 ssl;
ssl_preread off;
proxy_pass 127.0.0.1:6502;
}
}
I have been in a great search for this, unfortunately, it is not possible as TCP has no concept of server names, so this is not possible. It only works in HTTP because the client sends the hostname it is trying to access as part of the request, allowing NGINX to match it to a specific server block.
- Reference
- Found on this thread
Very upsetting as I really would've liked to direct TCP traffic based on url