TCP services such as databases, message queues, SSH gateways, and custom daemons need HAProxy to forward byte streams without trying to parse HTTP headers. A listener that accidentally inherits HTTP mode can break non-HTTP traffic, while an unchecked backend pool can keep sending clients to a closed port.
TCP mode keeps the frontend and backend at Layer 4. The frontend binds the address and port clients use, the backend lists the service nodes, the balance directive chooses a destination for each connection, and a check argument on each server line removes a failed node from rotation when the TCP check cannot connect.
The example below uses roundrobin so repeated test connections visibly reach more than one backend. For long-lived database or queue sessions, leastconn may fit better because new clients go to the server with fewer active connections. Use the service's native client for final validation when nc cannot complete the protocol handshake.
$ sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.before-tcp-lb
$ sudoedit /etc/haproxy/haproxy.cfg
frontend tcp_service
bind *:5000
mode tcp
timeout client 30s
default_backend tcp_service_nodes
backend tcp_service_nodes
mode tcp
balance roundrobin
option tcp-check
tcp-check connect
timeout connect 5s
timeout server 30s
server tcp-a 10.0.10.11:5000 check
server tcp-b 10.0.10.12:5000 check
Replace the listener port, backend IP addresses, and backend port with the real service values. Keep mode tcp in both sections when existing defaults use HTTP mode.
$ sudo haproxy -c -V -f /etc/haproxy/haproxy.cfg Configuration file is valid
The -V flag prints the success line. On current Ubuntu packages, sudo haproxy -c -f /etc/haproxy/haproxy.cfg can exit successfully without output.
$ sudo systemctl reload haproxy
The packaged systemd unit on current Ubuntu systems validates the configured file before sending the reload signal, but running the manual check first keeps failures visible before the service action.
Related: How to reload HAProxy gracefully
$ nc -w 2 proxy.example.net 5000 backend=tcp-a $ nc -w 2 proxy.example.net 5000 backend=tcp-b
A banner or echo-style test service can show the backend name directly. For databases, queues, SSH, or TLS-wrapped TCP services, use the matching client command and confirm the response comes through the proxy address.
Stop one backend service or block its service port, wait for the configured health check to mark it down, and confirm new client connections continue through the remaining backend.