skip to content

An HAProxy backend contains the lines `stick-table type ip size 1m expire 30m` and `stick on src`. What do those two lines make the backend do with a returning client, and what happens once an entry expires?

level: juniorimportance: should knowfreq 45%

answer

  1. in-memory keyed store, per backend
  2. key is the connection's source address
  3. stores which server was chosen
  4. expire is idle, refreshed on match
  5. NAT collapses many users to one key

basics

~20 s

HAProxy keeps one in-memory entry per client IP recording which server that IP was sent to, and routes the same IP back to that server on later requests. After 30 minutes with no match the entry is purged and the balance algorithm chooses again.

solid answer

~50 s

`stick-table type ip size 1m expire 30m` declares an in-memory table in that backend, keyed by an IPv4 address, holding up to one million entries, each expiring after 30 minutes of inactivity. `stick on src` is shorthand for match-and-store on the `src` fetch: on the first request from a client HAProxy runs the normal `balance` algorithm, then stores the chosen server's id under that client's source address; on every later request it looks the address up and, if the entry exists and that server is still usable, sends the request straight there and skips balancing. The `expire` timer is an idle timer — it is refreshed each time the entry is matched — so an active client stays pinned indefinitely. Once it lapses the entry is purged, the next request is balanced normally, and the client may land somewhere else.

go deeper

for a junior

Be able to read the two lines out loud: a table keyed by client IP that remembers the chosen server, and a rule that pins that IP to it. Say plainly that the entry disappears after the idle window.

for a middle

Explain that stick on is match plus store-request, that size counts entries rather than bytes, and that the expiry timer is refreshed on every match rather than running from creation.

for a senior

Show where source-address affinity breaks in production — NAT and CGNAT collapsing users onto a few servers, mobile clients changing address mid-session — and say why you would reach for cookie persistence instead.

for a principal

Own the position that affinity is an optimisation, never a correctness mechanism: argue for externalising session state so that losing a pin costs latency, not a logged-out user, and set that as a platform default.

## What a stick table is A stick table is HAProxy's own small in-memory key/value store. Every entry is keyed by something HAProxy can extract from the traffic — a source address, a header value, a cookie, a URL parameter — and carries a payload: by default just the id of the server that request went to, and optionally a set of counters you ask for with `store`. It lives in the HAProxy process's memory, is scoped to the proxy section that declares it, and is not a database: nothing is written to disk, and a process restart starts from an empty table. ## Reading the declaration ``` backend app balance roundrobin stick-table type ip size 1m expire 30m stick on src server s1 10.0.0.11:8080 check server s2 10.0.0.12:8080 check ``` - `type ip` — the key is an IPv4 address. Other types include `ipv6`, `integer`, `string` and `binary`; a `string` key normally also takes a `len` so HAProxy knows how much of it to keep. - `size 1m` — one million entries, not one megabyte. The suffix counts entries. Each entry costs on the order of fifty bytes plus whatever data types you store, so the sizing decision is really a memory decision. - `expire 30m` — how long an entry survives without being touched. With no `store` clause the entry holds only the server id, which is exactly what stickiness needs. ## What `stick on src` actually does `stick on <expr>` is the combination of two more primitive rules: `stick match <expr>`, which looks the key up on the way in, and `stick store-request <expr>`, which writes the chosen server back after the load-balancing decision. `src` is the sample fetch for the source address of the connection as HAProxy sees it. So the sequence for a brand-new client is: no entry found, `balance roundrobin` picks `s2`, the request is proxied, and an entry `10.20.30.40 -> s2` is created. The next request from that address matches, and HAProxy sends it to `s2` without consulting the balance algorithm at all. This is why a table-based affinity policy and a balancing algorithm are not in competition: the table wins whenever it has an answer. If the stored server has since been taken out of rotation — it failed its checks, or it was disabled — the entry cannot be honoured and the request falls back to normal balancing. ## When the entry goes away The `expire` value is an inactivity timeout, not a lifetime: HAProxy refreshes the timer whenever the entry is created, matched or updated. A client that keeps browsing keeps its pin; a client that goes quiet for longer than the window loses it silently, with no signal to the application. Entries also disappear when the table is full — HAProxy purges the oldest entries to make room unless the table is declared `nopurge` — and when the process is replaced without a mechanism to hand the table over. ## Where source-address stickiness stops working The key is the TCP peer address, so everything that rewrites or shares that address defeats it. Users behind one corporate NAT, a mobile carrier's CGNAT, or a CDN all present as a handful of addresses, so they collapse into a few entries and pile onto a few servers. Conversely a mobile client that changes networks gets a new address and loses its pin mid-session. That is the practical reason HAProxy also offers cookie-based persistence, which keys on something the client carries rather than where it appears to come from. ## What to keep in mind Stickiness is a per-process, per-node structure. Two HAProxy nodes each keep their own table unless you replicate them, and the table is best treated as a hint that improves cache locality rather than a guarantee your application can depend on for correctness.

  • How does `stick on src` differ from `stick match src` and `stick store-request src`?
    `stick on` is simply both of the others applied to the same expression. `stick match` only performs the lookup on the way in; `stick store-request` only writes the chosen server afterwards. Splitting them is useful when you want to match on one expression and store under another — for example matching a cookie in the request but storing under a value only visible later.
  • What happens to a pinned client when the stored server fails its health check?
    The entry can no longer be honoured, so HAProxy falls back to the backend's `balance` algorithm and the request goes to a healthy server. The client's session state on the old server is gone, which is why affinity should improve locality rather than be load-bearing for correctness.
  • Does `size 1m` mean one megabyte of memory?
    No — it means one million entries. Memory follows from that: roughly fifty bytes per entry plus the size of any data types listed in `store`. Sizing the table is really about how many distinct keys you expect to be live inside the `expire` window.

saying these in an interview costs you the question

  • Thinking size is memory rather than an entry count
  • Believing expire counts from creation, not last match
  • Assuming the table survives a process restart by itself
  • Treating source-IP affinity as reliable behind NAT or a CDN
  • Thinking stick on src replaces the balance algorithm entirely

context