When working with multiple game servers on a single host machine, we often need to route TCP connections based on hostnames (like server1.domain.net vs server2.domain.net). Unlike HTTP traffic, raw TCP connections don't contain host headers, making this seemingly simple task quite challenging.
The configuration you attempted:
listen game-listener
bind x.x.x.x:22222
mode tcp
use-server server1 if { hdr(host) -i server1.domain.net }
use-server server2 if { hdr(host) -i server2.domain.net }
server server1 localhost:22201 check
server server2 localhost:22202 check
fails because hdr(host)
only works in HTTP mode. In TCP mode, HAProxy doesn't inspect application-layer data.
Option 1: Use SNI with TLS Termination
If your game clients support TLS, you can leverage SNI (Server Name Indication):
frontend game-frontend
bind :22222 ssl crt /etc/ssl/certs/game.pem
mode tcp
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
use_backend server1 if { req_ssl_sni -i server1.domain.net }
use_backend server2 if { req_ssl_sni -i server2.domain.net }
backend server1
server s1 localhost:22201
backend server2
server s2 localhost:22202
Option 2: Separate Ports per Hostname
The simplest solution when SNI isn't available:
frontend fe_server1
bind :22223
mode tcp
default_backend server1
frontend fe_server2
bind :22224
mode tcp
default_backend server2
Option 3: PROXY Protocol
If you control both ends of the connection, consider using PROXY protocol:
frontend game-frontend
bind :22222
mode tcp
tcp-request content accept if { req.proxy_hello }
use_backend server1 if { req.proxy_sni -i server1.domain.net }
use_backend server2 if { req.proxy_sni -i server2.domain.net }
To verify your configuration:
# For SNI testing
openssl s_client -connect yourdomain:22222 -servername server1.domain.net
# For basic TCP testing
nc -zv yourdomain 22222
telnet yourdomain 22222
When routing TCP by hostname:
- Enable
tune.ssl.cachesize
when using TLS - Set appropriate
timeout client
values for your game protocol - Consider using
balance source
for sticky connections
When dealing with multiple game servers running on a single machine, traditional HTTP-based routing solutions don't work because TCP traffic doesn't contain host headers. This creates a unique challenge for server administrators who need to route connections based on subdomains.
The initial approach using hdr(host)
checks fails because:
- TCP is a lower-level protocol than HTTP
- Host information isn't part of the TCP handshake
- Game protocols typically don't include host headers
If your game servers use TLS/SSL, you can leverage Server Name Indication (SNI):
listen game-tls
bind x.x.x.x:22222 ssl crt /path/to/cert.pem
mode tcp
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
use-server server1 if { req_ssl_sni -i server1.domain.net }
use-server server2 if { req_ssl_sni -i server2.domain.net }
server server1 localhost:22201 check
server server2 localhost:22202 check
For non-TLS connections, consider these options:
Port-Based Routing
listen game-ports
bind x.x.x.x:22223
mode tcp
use-server server1 if { dst_port 22223 }
use-server server2 if { dst_port 22224 }
server server1 localhost:22201 check
server server2 localhost:22202 check
Separate IP Addresses
Assign multiple IPs to your host and bind each server to a different IP:
listen game-ip1
bind 192.168.1.101:22222
mode tcp
server server1 localhost:22201 check
listen game-ip2
bind 192.168.1.102:22222
mode tcp
server server2 localhost:22202 check
For maximum compatibility with existing setups:
- Use SRV records for game-specific DNS routing
- Implement a TCP proxy that reads initial handshake packets (game-specific)
- Consider client-side solutions that include server identifiers in their protocol