HAProxy Basics: TCP and HTTP Modes and High Availability

HAProxy Basics: TCP and HTTP Modes and High Availability

High availability starts with choosing the right inspection level for traffic. You can either forward raw TCP streams or parse full HTTP messages and apply routing and policy. Once you’re clear about that line, the rest—health checks, balancing, and failover—becomes a set of predictable knobs in HAProxy.

We’ll anchor the concepts in practical patterns: TLS passthrough when apps own certificates, HTTP header routing for multi-tenant APIs, and health checks that detect partial failures (ports open but responses wrong). We’ll also cover how to keep traffic flowing across node failures and during configuration reloads, and what to log so you can pinpoint latency and errors quickly.

Keep the mental model simple: a frontend accepts client connections, one or more backends hold server pools, and a defaults section centralizes timeouts and logging. From there, you decide the mode (TCP vs HTTP), how to balance, and how HAProxy decides a server is healthy.

Choosing the Right Mode: TCP vs HTTP

TCP mode operates at layer 4 and forwards streams without understanding application protocols. It’s a fit for TLS passthrough, databases, SMTP, MQTT, and custom protocols. Because HAProxy doesn’t assemble HTTP requests here, features that rely on HTTP semantics aren’t available.

HTTP mode parses and can modify messages at layer 7. That enables routing by Host and path, header-based ACLs, URL rewrites, cookie insertion, and richer logging. You can terminate TLS at HAProxy, advertise ALPN for HTTP/2 on the client side, and still speak HTTP/1.1 to your servers.

When TCP Mode Shines

Use TCP when backends must see the raw TLS connection or terminate TLS themselves. A common pattern is SNI-based routing in TCP mode: add a small inspect delay, read the ClientHello, and steer by the SNI value without decrypting traffic. This keeps private keys on the application tier while letting the proxy split traffic per hostname.

When HTTP Mode Wins

Pick HTTP mode when you need content switching, header transformations, compression, caching-friendly normalization, or cookie-based stickiness. On the bind line, advertise ALPN so clients can use HTTP/2 while your backends can remain on HTTP/1.1 unless you explicitly enable h2 to the servers. With HTTP mode you also gain precise request/response timers and status-code visibility in logs.

Core Configuration Building Blocks

Frontends, Backends, and Defaults

Place common timeouts (connect, client, server) and log configuration in defaults to keep sections consistent. Frontends bind to addresses and ports and select a backend with use_backend or switch rules via ACLs. Backends set balance strategy, define server lines with health checks, and optional persistence or circuit-breaking policies.

Timeouts, Retries, and Queues

Treat timeouts as guardrails. connect covers the TCP handshake to the server, server covers inactivity while awaiting the response, and client covers inactivity toward the client. Keep HTTP keep-alives enabled unless you have a compelling reason to close. Use http-reuse in backends to pool and reuse server-side connections to cut handshake overhead. Be conservative with retries and, if needed, constrain them to idempotent methods so you don’t create duplicate side effects. Cap queue growth with maxqueue or a dedicated “buffering” backend to avoid unbounded latency during brownouts.

Balancing Algorithms and Persistence

roundrobin works well under steady load, leastconn reduces tail latency when request times vary, and source gives IP-hash stickiness when cookies aren’t available. For full control, enable cookie-based persistence: define a cookie at the backend and a unique cookie value on each server line so clients consistently hit the same node. When you need consistent decisions across multiple proxies, replicate stick-tables using a peers cluster.

Health Checks That Catch Real Failures

TCP Checks

In TCP mode, a basic check confirms the port is reachable. For deeper coverage, script tcp-check send/expect to validate a minimal handshake-like exchange for protocols that allow it. Tune sensitivity with rise/fall so brief blips don’t flap servers, and select a realistic inter interval.

HTTP Checks

In HTTP mode, option httpchk issues a request you define, like GET /health or HEAD /. Combine it with http-check expect to validate status codes or a marker string/regex in the body, which catches cases where an app returns 200 but is actually degraded. If your app has complex dependencies, expose a health endpoint that fails only when the service can’t perform its core work.

TLS, ALPN, and SNI in Checks

If backends require TLS, enable SSL for checks and set SNI so the check exercises the same hostname path as real traffic. You can also negotiate HTTP/2 for checks when appropriate. Use verify options and a CA file if you want checks to fail on certificate trust issues, which helps surface mis-issued or expired certs.

High Availability Patterns

Active-Passive with a Floating IP

Run two HAProxy instances and front them with a floating virtual IP using VRRP (for example, with keepalived). Only the active node answers; on failure, the VIP moves with it. This is simple to reason about and fits environments where you can control layer 2/3 behavior.

Active-Active with Anycast or DNS

Scale horizontally by running multiple HAProxy nodes and distributing clients across them. Two common approaches are anycast with route health injection or DNS round robin with multiple A/AAAA records. Either way, ensure you can gracefully drain a node during maintenance by removing its advertisement or pulling its DNS record with a short TTL.

State Sharing with Peers

For sticky sessions, rate limiting, or abuse detection, attach stick-tables to a peers section so entries replicate among nodes. That keeps decisions consistent when clients bounce between instances and preserves counters across rolling restarts.

Draining and Zero-Downtime Reloads

Before maintenance, set weight 0 or disable a server so it drains existing connections. On the proxy itself, use hitless reloads: spawn a new process with the updated config while the old one finishes existing connections, optionally handing off listener sockets. On systems that support it, a service reload is the standard way to achieve this; confirm via the stats socket or dashboard that new listeners are bound and old ones are quiescing.

Observability and Operations

Logging and Stats

In TCP mode, use tcplog; in HTTP mode, use httplog to capture status codes and granular timers like Tq, Tw, Tc, Tr, and total. These fields make bottlenecks obvious: connect time spikes point at network or server saturation, while long response times point at application latency. Enable the stats dashboard or admin socket so you can inspect backends, queues, health state, and stick-table counters in real time.

Capacity and Performance Tips

Right-size maxconn based on memory and file descriptors, and ensure system ulimits and sysctl values allow your target concurrency. Favor nbthread (multi-threading) over nbproc (multi-process) unless you have a specific need for multiple processes; threads avoid state split issues with stick-tables and health checks. Terminate TLS with modern ciphers where possible, pre-load certificates, and pool server connections with http-reuse if your application tolerates it. Always match server timeouts to observed latency plus jitter rather than round numbers.

Putting It Together: Example Decisions

A public site with multiple hostnames on one IP can stay in TCP mode and route by SNI if services hold the certificates; otherwise, terminate at the proxy and use HTTP mode with host-based routing and cookie persistence. Internal API gateways almost always use HTTP mode for header-based policy and structured logs. Databases, message brokers, and mail protocols stay in TCP mode, often with scripted TCP checks.

Common Failure Modes and Fixes

Intermittent 5xx at steady CPU often indicates queueing from too-small maxconn or slow backends; check Tw and backend queue metrics. “connect” timeouts suggest routing or firewall issues, while long “response” times point toward application latency or dependency stalls. Flapping health checks typically result from aggressive inter/fall settings or checks hitting a slow dependency—relax thresholds and keep the health endpoint lean.

Security Considerations

Terminate TLS at the edge when you need header management, protocol normalization, or WAF; use passthrough when you only need SNI steering and want keys on the app tier. In both cases, run HAProxy with minimal privileges, restrict filesystem access to certificate directories, sanitize hop-by-hop headers, and set X-Forwarded-* consistently so apps receive the correct client address and scheme.

HAProxy TCP and HTTP Modes & High Availability (FAQ)

Use TCP for opaque protocols or TLS passthrough; use HTTP when you need routing by headers/paths, message rewrites, cookie stickiness, compression, or detailed request logs.

Yes, enable a short inspect delay in TCP mode and match on the ClientHello’s SNI so you can split traffic per hostname while keeping certificates on the backend.

Call a fast /health endpoint and use an expect rule to match a marker string or regex, which catches partial failures even when the port is up and returning 200.

Test resolution externally with a quick DNS Lookup and configure resolvers in HAProxy so changes roll in at runtime without a full reload.

Use hitless reloads so a new process takes over listeners while the old one drains; confirm progress via the stats socket or dashboard before stopping the old worker.

Persist with cookies in the backend and replicate stick-tables through a peers cluster so decisions stay consistent when clients hit different nodes.

Bind on IPv4 and IPv6 and verify upstream reachability; if users report issues, run an IPv6 Test to confirm end-to-end support.

Export client IPs from HAProxy logs and, when needed, resolve them with a Reverse DNS Lookup to see associated PTR records.