How to Proxy a Large Port Range (9000-9999) in Nginx with Minimal Configuration


2 views

When needing to proxy an entire range of ports (say 9000-9999) to equivalent ports on a backend server, the naive approach would be writing individual server blocks for each port. This quickly becomes unmanageable:

# Painful manual configuration example - DON'T DO THIS
server {
    listen 9000;
    proxy_pass backend:9000;
}

server {
    listen 9001;
    proxy_pass backend:9001;
}
# ...and 999 more blocks!

Nginx's stream module (available since version 1.9.0) supports port ranges in listen directives:

stream {
    server {
        listen 9000-9999;
        proxy_pass backend:$server_port;
    }
}

The magic happens with $server_port which dynamically captures the incoming port number and reuses it for the backend connection.

Here's a full working configuration with important optimizations:

worker_processes auto;
error_log /var/log/nginx/error.log;

events {
    worker_connections 1024;
}

stream {
    # TCP/UDP proxy for port range
    server {
        listen 9000-9999;
        proxy_pass backend_server:$server_port;
        proxy_timeout 60s;
        proxy_connect_timeout 2s;
    }
    
    # Optional: UDP specific configuration
    server {
        listen 9000-9999 udp;
        proxy_pass backend_server:$server_port;
        proxy_timeout 60s;
        proxy_responses 1;
    }
}

http {
    # Regular HTTP configuration
    # ... (your existing HTTP config)
}

When dealing with 1000+ ports:

  • Set worker_rlimit_nofile to a high value (like 65535) in main context
  • Increase worker_connections in events block
  • Consider separating TCP and UDP traffic if both are needed
  • Monitor netstat -tuln to verify all ports are bound

For more complex routing logic, you can use OpenResty's Lua module:

server {
    listen 9000-9999;
    
    access_by_lua_block {
        ngx.var.backend_port = ngx.var.server_port
    }
    
    proxy_pass http://backend:$backend_port;
}

After configuration:

# Check bound ports
ss -tulnp | grep nginx

# Test connectivity
for port in {9000..9005}; do
    echo "Testing port $port"
    curl -v http://localhost:$port
done

When dealing with large port ranges (e.g., 9000-9999), manually configuring each port in Nginx would be tedious and inefficient. The goal is to proxy all traffic from ports 9000-9999 on the local machine to the same ports on a different IP address without writing thousands of lines of configuration.

The ngx_stream_core_module (introduced in Nginx 1.9.0) is perfect for this scenario. It allows TCP/UDP proxying without requiring individual server blocks for each port.


load_module /usr/lib/nginx/modules/ngx_stream_module.so;

Here's how to configure Nginx to handle the entire port range dynamically:


stream {
    server {
        listen 9000-9999;
        proxy_pass backend_server:$server_port;
    }

    upstream backend_server {
        server destination_ip:9000-9999;
    }
}

For more complex scenarios where you need additional processing, you can use variables:


stream {
    map $remote_port $backend_port {
        default $remote_port;
    }

    server {
        listen 9000-9999;
        proxy_pass destination_ip:$backend_port;
    }
}

When proxying large port ranges:

  • Monitor connection limits (worker_connections)
  • Consider using reuseport for better performance
  • Adjust kernel parameters for large numbers of sockets

Remember that:

  • All ports in the range will be exposed
  • Firewall rules should be configured accordingly
  • Consider rate limiting if applicable

After implementing, test with:


nginx -t
systemctl restart nginx

Then verify connectivity to any port in the range (e.g., 9000, 9500, 9999).