In HAProxy, what changes when a proxy runs in `mode tcp` instead of `mode http`, and what must be true about the mode of a frontend and the backends it routes to?
answer
- how deep the proxy looks
- bytes relayed versus requests parsed
- one decision per connection, or per request
- the handshake is still readable in the clear
- both ends of a route must agree
basics
~20 sIn mode http HAProxy parses each request, so HTTP fetches, http-request rules and per-request balancing work. In mode tcp it relays bytes, balancing once per connection with only L4 and TLS-hello fetches. A frontend and the backends it uses must share the same mode or the config is rejected.
solid answer
~40 s`mode` decides how deeply HAProxy looks at the stream. In `mode http` it parses every request, so `path`, `hdr()` and the rest of the HTTP fetches work, `http-request` rules apply, `option httplog` produces per-request logs, and a load-balancing decision is made per request rather than per connection. In `mode tcp` HAProxy relays bytes and knows nothing above L4: one balancing decision at connection time, `option tcplog`, and only L4 fetches — plus TLS ClientHello fetches such as `req.ssl_sni` if you add `tcp-request inspect-delay` and wait for the handshake bytes. The mode must be consistent: an HTTP frontend cannot route to a TCP backend, and HAProxy rejects that config at startup rather than degrading silently. Choose `mode tcp` when the payload is not HTTP, or when you must pass TLS through untouched.
go deeper
Be able to say that mode http means HAProxy reads the requests and mode tcp means it just relays bytes, and that mode is usually set once in defaults.
List concretely what TCP mode costs you: no HTTP fetches, no http-request rules, no header insertion, one balancing decision per connection, connection-level logs. Know the frontend and backend must share a mode.
Show you can still route TLS passthrough on SNI using tcp-request inspect-delay and req.ssl_sni, and explain the mode mismatch as a startup-time rejection you catch with haproxy -c in CI rather than in production.
Own the decision itself: which traffic classes get an L7 proxy and which stay opaque, what key custody or compliance requires, and how the config is structured so a non-HTTP proxy cannot accidentally inherit HTTP mode.
## What mode actually selects `mode` is set in `defaults`, or per proxy, and it selects which protocol machinery HAProxy runs over the connection. It is the single knob that decides whether this proxy operates at the connection level or the request level — the general L4-versus-L7 trade, expressed as one HAProxy keyword. ``` defaults mode http timeout connect 5s timeout client 30s timeout server 30s listen postgres mode tcp bind :5432 server db1 10.0.0.31:5432 ``` ## mode http HAProxy parses the request line and headers, and keeps parsing for the life of the connection. That unlocks: - **HTTP fetches in ACLs**: `path`, `path_beg`, `hdr(host)`, `method`, `url_param`, `base`. - **Rewriting and policy**: the `http-request` and `http-response` families — `set-header`, `del-header`, `replace-path`, `redirect`, `deny`, `return`. - **Per-request load balancing.** Two requests on one client connection can go to two different servers, because HAProxy owns both sides of the conversation and multiplexes onto its own server connections. - **Per-request logging** with `option httplog`, giving method, path, status and timing per request rather than per connection. - **HTTP/2**, which requires HTTP mode: `bind :443 ssl crt ... alpn h2,http/1.1` on the client side, `server ... proto h2` toward a server that speaks it. The cost is real work per request — parsing, header manipulation, and, for HTTPS, terminating TLS so there is something to parse at all. ## mode tcp HAProxy copies bytes in both directions and never interprets them. Consequences: - **One balancing decision per connection.** Every byte of that connection goes to the same server, whatever the payload contains. - **Only L4 information** for routing by default: `src`, `dst`, `dst_port`. - **`option tcplog`**, which logs the connection, not requests. There is no status code to log because HAProxy never saw one. - **No header insertion.** `option forwardfor` is an HTTP feature and does nothing here, so the client address has to travel via the PROXY protocol instead. - **Rules come from the `tcp-request` family**, not `http-request`. This is what you use for anything that is not HTTP — a database, a message broker, plain SMTP — and for TLS you deliberately do not want to terminate. ## Routing on SNI without terminating TLS TCP mode is not entirely blind. HAProxy can buffer the first bytes of the connection and inspect the TLS ClientHello, which carries the server name in clear: ``` frontend tls_passthrough mode tcp bind :443 tcp-request inspect-delay 5s tcp-request content accept if { req.ssl_hello_type 1 } use_backend api_tls if { req.ssl_sni -i api.example.com } default_backend web_tls ``` `tcp-request inspect-delay` gives HAProxy a window to wait for enough bytes; the `accept` rule fires as soon as a ClientHello is present, and `req.ssl_sni` reads the name from it. The private key never leaves the servers. What you lose is everything above the handshake: no path routing, no header rewriting, no per-request logs — because the rest of the stream is encrypted to a key HAProxy does not hold. ## The mode must be consistent A frontend and every backend it can select must run in the same mode. HAProxy validates this when it parses the config: an HTTP frontend pointing at a TCP backend is rejected at startup, so `haproxy -c -f` catches it before a reload. This is a mercy rather than a nuisance — a silent downgrade would break header insertion and per-request routing in ways that only show up under traffic. The usual way this bites is inheritance. `defaults` sets `mode http`, someone adds a `listen` block for a database and forgets `mode tcp`, and HAProxy tries to parse the wire protocol as HTTP. From HAProxy 2.4 you can keep a second, named defaults section — `defaults tcp-common` with `mode tcp` — and write `listen postgres from tcp-common`, which removes the whole class of mistake. ## Choosing Go to `mode http` when you want routing on paths or headers, header insertion, per-request balancing, retries on idempotent requests, or HTTP-aware logging — that is, most web traffic. Stay in `mode tcp` when the protocol is not HTTP, when compliance or key custody says TLS must not be terminated at the proxy, or when raw throughput on long-lived connections matters more than any per-request decision.
- In mode tcp, how does the backend learn the client's real IP address?Through the PROXY protocol, not a header — there is no HTTP message to insert one into. Add `send-proxy` or `send-proxy-v2` to the `server` line and configure the receiving server to expect it. `option forwardfor` is an HTTP-mode feature and has no effect in TCP mode.
- Why can HAProxy route on SNI in mode tcp but not on the request path?The TLS ClientHello is sent in the clear before the session keys exist, so `req.ssl_sni` can read the server name after `tcp-request inspect-delay` buffers the first bytes. Everything after the handshake, including the request path, is encrypted to a key HAProxy does not hold in passthrough.
- What happens if a defaults section sets mode http and a listen block for a database omits mode tcp?The listen block inherits HTTP mode and HAProxy tries to parse the database wire protocol as HTTP, which fails immediately for every connection. Fix it by setting `mode tcp` explicitly in that proxy, or, on 2.4 and later, by inheriting from a named `defaults tcp-common` section with `from`.
saying these in an interview costs you the question
- Thinks mode tcp still allows path or header ACLs
- Says HAProxy silently downgrades an HTTP frontend to match a TCP backend
- Believes option forwardfor works in TCP mode
- Claims TCP mode makes a new balancing decision per request
- Assumes TLS passthrough still lets the proxy rewrite headers