skip to content

Frontend/Backend Model & ACL Routing

This is HAProxy's config shape: global and defaults, then frontends that bind and classify traffic and backends that hold the server pool, with ACLs deciding which backend a request lands in. I get asked it because reading a `use_backend` chain and knowing when `mode tcp` beats `mode http` is the fastest way to show I actually run this proxy rather than copy snippets.

on this pageshow

questions

5

Walk through the shape of an HAProxy configuration file: what do the global, defaults, frontend and backend sections each configure, and what does a listen section do instead?

level: juniorimportance: must knowfreq 74%

answer

  1. four section kinds, two of them proxies
  2. process settings versus traffic settings
  3. one side binds, the other holds servers
  4. a template that only applies downward
  5. listen = bind and server together

basics

~20 s

HAProxy config is split into global (process-wide settings), defaults (a template inherited by every proxy declared after it), frontend (a bind address that classifies incoming traffic), and backend (the server pool). A listen section fuses a frontend and backend into one block.

solid answer

~40 s

An HAProxy config is a list of sections. `global` configures the process itself — privileges, `daemon`, thread count, process-wide `maxconn`, logging targets, default TLS settings. `defaults` is not a proxy at all; it is a template whose settings are inherited by every `frontend`, `backend` and `listen` declared **after** it, which is why a second `defaults` block silently changes everything below it and nothing above. A `frontend` owns the `bind` line and decides where traffic goes: `acl` definitions, `http-request` rules, `use_backend` and `default_backend`. A `backend` owns the pool — `server` lines, `balance`, health checks, persistence. A `listen` section simply combines both: it can hold `bind` and `server` lines together, which is handy for a simple one-to-one TCP proxy. Validate any of it with `haproxy -c -f /etc/haproxy/haproxy.cfg` before reloading.

go deeper

for a junior

Be able to read a small haproxy.cfg out loud and say which section each line belongs to. The one-liner to have ready: the frontend binds and decides, the backend holds the servers.

for a middle

Explain that defaults is a positional template, not a merge, and that a second defaults block resets it for everything below. Know that global never contains bind or server lines.

for a senior

Show that you validate with haproxy -c before a reload and that you standardise mode and the timeout family in defaults so no proxy can ship without them. Say why you split listen into frontend/backend once routing appears.

for a principal

Own the config layout for a fleet: generated versus hand-edited, named defaults sections for mixed HTTP and TCP proxies, unique proxy naming, and where the syntax check sits in the pipeline that produces the file.

## Why the file is shaped this way HAProxy separates *how the process runs* from *how traffic is received* from *where traffic goes*. Those three concerns map onto `global`, `frontend` and `backend`, with `defaults` acting as a template so you do not repeat the same twelve lines in twenty proxies. Reading a config fluently is mostly knowing which concern each keyword belongs to. ``` global maxconn 20000 log /dev/log local0 user haproxy group haproxy daemon defaults mode http timeout connect 5s timeout client 30s timeout server 30s frontend public bind :80 acl is_api path_beg /api/ use_backend api if is_api default_backend web backend web balance roundrobin server web1 10.0.0.11:8080 server web2 10.0.0.12:8080 backend api server api1 10.0.0.21:9000 ``` ## global — the process `global` describes the running program, not traffic. It holds privilege dropping (`user`, `group`), whether to background (`daemon`), the threading model (`nbthread`), logging destinations (`log`), default TLS material such as `ssl-default-bind-ciphers` and `ssl-default-bind-options`, and a process-wide `maxconn` that caps concurrent connections across every proxy. It never contains `bind` or `server` lines — if you find yourself wanting to put a listening address here, you want a frontend. ## defaults — a template, not a proxy `defaults` accepts most of the same keywords a frontend or backend accepts, but it never serves traffic. Its settings are copied into each proxy declared **after** it in the file. Two consequences trip people up. First, it is positional: a `defaults` block placed at the bottom of the file affects nothing. Second, declaring a second `defaults` block replaces the template for everything below it — the two are not merged, so a `backend` after the second block does not inherit the first block's timeouts. From HAProxy 2.4 you can name defaults sections and select one explicitly, for example `defaults tcp-common` followed by `backend db from tcp-common`, which makes mixed HTTP/TCP configs far less accident-prone. Almost every real config puts `mode`, the `timeout` family and `option httplog` here, because forgetting a timeout in a proxy is a production outage waiting to happen. ## frontend — where traffic arrives and gets classified A `frontend` must have at least one `bind` line, which is the socket: `bind :80`, `bind :443 ssl crt /etc/haproxy/certs/site.pem`, optionally with `alpn h2,http/1.1` or `accept-proxy`. Everything else in a frontend is about *classification and decision*: `acl` statements that name a condition, `http-request` rules that inspect or rewrite, `use_backend` rules that route, and `default_backend` as the fallback. A frontend has no idea what a server is; if no `use_backend` matches and there is no `default_backend`, HAProxy has nowhere to send the request and answers 503. ## backend — the pool A `backend` has no `bind`. It holds `server` lines (`server web1 10.0.0.11:8080 check maxconn 100`), the `balance` algorithm, health-check directives, persistence, and its own `http-request`/`http-response` rules which run after the frontend's. Backends are referenced by name, and names must be unique across all proxies in the file. ## listen — both at once `listen` is a frontend and a backend in one section, so it can carry `bind` and `server` lines together: ``` listen mysql mode tcp bind :3306 server db1 10.0.0.31:3306 ``` There is nothing second-class about it; it is the same machinery with one name. It reads well for a simple pass-through where one listening port maps to one pool, and reads badly as soon as you want several backends behind one port — at that point split it, because only a frontend/backend pair lets several frontends share a backend or one frontend fan out. ## Checking it `haproxy -c -f /etc/haproxy/haproxy.cfg` parses the file and reports errors without starting the proxy. Run it in CI and before every reload; most of the mistakes above (an unknown backend name, a `server` line in the wrong section, a proxy with no timeouts) are caught there rather than in production.

  • Where would you put the mode and timeout settings, and why does it matter that defaults is positional?
    Put `mode` and the `timeout connect`/`client`/`server` family in `defaults` so no proxy is left without them. It matters because a `defaults` block applies only to proxies declared after it, and a second `defaults` block replaces the template rather than merging with it — so moving a backend above or below a defaults section silently changes its behaviour.
  • A frontend has no default_backend and no use_backend rule matches. What does the client get?
    A 503. The frontend parsed the request fine but has no destination, so HAProxy generates the error itself. In the logs the termination state shows the request never reached a server. Always give a frontend an explicit `default_backend`, even if it only serves a static error or a maintenance page.
  • When would you split a listen section into a separate frontend and backend?
    As soon as more than one destination or more than one entry point is involved: several backends selected by ACLs behind one bind, or two frontends (say plain HTTP and HTTPS) sharing one pool. A `listen` block hard-couples one bind to one pool, so it stops paying off the moment routing appears.

saying these in an interview costs you the question

  • Thinks defaults applies to proxies declared above it
  • Puts server lines in a frontend or bind in a backend
  • Believes global holds listening addresses
  • Says listen is deprecated or behaves differently from frontend plus backend
  • Cannot name which section default_backend belongs to

context

open as a page

In an HAProxy frontend with several `use_backend` rules and a `default_backend`, how does HAProxy decide which backend a request goes to, and how do multiple ACLs in one condition combine?

level: middleimportance: must knowfreq 66%

basics

~20 s

HAProxy evaluates use_backend rules top to bottom and the first one whose condition is true wins; if none match it uses default_backend. Space-separated ACL names in a condition are AND-ed, || is OR, and ! negates.

open as a page

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?

level: middleimportance: should knowfreq 58%

basics

~20 s

In 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.

open as a page

Applications behind an HAProxy tier log every request as coming from the proxy's own address. Which HAProxy settings restore the real client address, and what breaks when the two ends disagree about the PROXY protocol?

level: seniorimportance: should knowfreq 50%

basics

~20 s

HAProxy opens its own connection to the server, so the source address is the proxy's. In HTTP mode option forwardfor adds the client address as a header; for TCP or TLS passthrough the PROXY protocol carries it, via send-proxy on the server line and accept-proxy on a receiving bind.

open as a page

What does HAProxy do with a request when every server in a backend has reached the `maxconn` set on its `server` line, and how does that limit differ from `maxconn` in the global section?

level: seniorimportance: nice to knowfreq 34%

basics

~20 s

HAProxy holds the request in the backend's queue until a server slot frees, bounded by timeout queue, after which the client gets 503. Server-line maxconn caps concurrent connections to one server; global maxconn caps them for the whole process.

open as a page