How to Proxy TCP Connections by Hostname with HAProxy for Game Servers


2 views

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