WebSocket traffic starts as an HTTP request and then upgrades into a long-lived tunnel. A working HAProxy configuration must preserve the upgrade handshake and keep the upgraded connection open longer than an ordinary short HTTP request.

HAProxy can route WebSocket requests in HTTP mode because the initial handshake uses HTTP headers such as Connection and Upgrade. After the backend returns 101 Switching Protocols, timeout tunnel controls how long HAProxy allows the upgraded stream to stay idle.

Set WebSocket timeouts deliberately. A normal HTTP server timeout can close quiet WebSocket clients too early, while an unlimited tunnel timeout can hold resources long after clients disappear.

Steps to configure HAProxy for WebSocket traffic:

  1. Confirm the WebSocket backend is reachable from the HAProxy host.
    $ nc -zv 10.0.30.11 8080
    Connection to 10.0.30.11 8080 port [tcp/*] succeeded!

    A TCP connect check proves only reachability. The upgrade test later proves that the backend accepts WebSocket handshakes through HAProxy.

  2. Open the active HAProxy configuration file.
    $ sudoedit /etc/haproxy/haproxy.cfg
  3. Add a frontend route and WebSocket backend.
    /etc/haproxy/haproxy.cfg
    frontend fe_http
        bind :80
        mode http
        acl is_websocket hdr(Upgrade) -i websocket
        use_backend be_websocket if is_websocket
        default_backend be_http
     
    backend be_http
        server app1 10.0.10.11:8080 check
     
    backend be_websocket
        mode http
        option http-server-close
        timeout tunnel 1h
        server ws1 10.0.30.11:8080 check

    hdr(Upgrade) -i websocket matches the upgrade request. timeout tunnel applies after the connection switches protocols.

  4. Set maxconn in frontend fe_http to the peak number of WebSocket clients.
    /etc/haproxy/haproxy.cfg
    frontend fe_http
        bind :80
        mode http
        maxconn 20000

    Long-lived sockets consume file descriptors, memory, and backend capacity even when they are idle. Each client holds one socket to the client and one to the backend, so 20000 clients need about 40000 file descriptors. An idle tunnel closes when timeout tunnel expires, so the value must exceed the gap between the application's ping or heartbeat messages.
    Related: How to configure connection limits in HAProxy
    Tool: WebSocket Connection Capacity Calculator

  5. Validate the complete HAProxy configuration.
    $ sudo haproxy -c -V -f /etc/haproxy/haproxy.cfg
    Configuration file is valid
  6. Reload HAProxy after validation succeeds.
    $ sudo systemctl reload haproxy
  7. Send a raw WebSocket upgrade probe through HAProxy.
    $ printf 'GET /ws HTTP/1.1\r\nHost: ws.example.net\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n' | nc -w 2 www.example.net 80
    HTTP/1.1 101 Switching Protocols
    upgrade: websocket
    connection: Upgrade
    sec-websocket-accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    x-backend: ws1

    HTTP 101 proves the request reached a WebSocket-capable backend and the upgrade headers survived the HAProxy route.

  8. Check ordinary HTTP traffic still reaches the default backend.
    $ curl -sS -i http://www.example.net/
    HTTP/1.1 200 OK
    
    plain HTTP backend
  9. Check the HAProxy log for the closed WebSocket connection.
    $ sudo grep be_websocket /var/log/haproxy.log
    2026-11-07T06:03:36.939104+00:00 haproxy1 haproxy[3009]: 127.0.0.1:42916 [07/Nov/2026:06:03:34.909] fe_http be_websocket/ws1 0/0/1/14/2029 101 144 - - ---- 1/1/0/0/0 0/0 "GET /ws HTTP/1.1"

    HAProxy writes this line when the connection closes, so a client that is still connected does not appear yet. be_websocket/ws1 and status 101 show the upgrade reached the WebSocket server, and cD in place of ---- means an idle timeout closed the connection.
    Related: How to configure HAProxy logging

    If WebSocket clients disconnect at a fixed interval, compare timeout client, timeout server, timeout tunnel, backend idle timeout, and any firewall or CDN idle timeout in front of HAProxy.

Share this guide