HAProxy HTTP log: timers and termination states
An HAProxy log line tells you more than most access logs: how long the request waited in a queue, how long the TCP connect took, how long the server thought, and β in four letters β who ended the stream and in which phase. This page is the reference for reading it during an incident.
Drop a haproxy.log on this page and the board decodes the termination states into plain language, draws each request as queue / connect / server / transfer bars, and builds the frontend β backend β server map from the log alone. Nothing is uploaded.
The HTTP log format
option httplog in a frontend turns on the HTTP format. The manual defines it as this exact log-format string:
# strict equivalent of "option httplog"
log-format "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC \
%CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r"A line as it lands in a file through syslog:
Oct 18 10:02:02 lb1 haproxy[2201]: 198.51.100.7:40110 [18/Oct/2026:10:01:57.020] fe_http be_api/api-3 0/0/0/120/5032 200 65536 - - SD-- 315/301/44/15/0 0/0 "GET /api/export HTTP/1.1"| # | awk field | Field | In the example | Meaning |
|---|---|---|---|---|
| β | $1β$4 | syslog header | Oct 18 10:02:02 lb1 | Added by syslog, not by HAProxy. Its width decides every awk position below. |
| 1 | $5 | process_name[pid]: | haproxy[2201]: | Process name and pid. |
| 2 | $6 | client_ip:client_port | 198.51.100.7:40110 | Client of the TCP connection. With accept-proxy and the PROXY protocol, the forwarded address. |
| 3 | $7 | [request_date] | [18/Oct/2026:10:01:57.020] | When the first byte of the request was received (%tr). Not when the line was written. |
| 4 | $8 | frontend_name | fe_http | Frontend that accepted the connection. |
| 5 | $9 | backend/server | be_api/api-3 | Backend chosen and the last server tried. <NOSRV> when no server was reached; <STATS> for the stats page. |
| 6 | $10 | TR/Tw/Tc/Tr/Ta | 0/0/0/120/5032 | Timers in milliseconds β next section. |
| 7 | $11 | status_code | 200 | Status returned to the client. Usually the server's, but HAProxy sets it when the server could not be reached or the response was blocked. |
| 8 | $12 | bytes_read | 65536 | Bytes sent to the client, headers included. A 64-bit counter. |
| 9 | $13 | captured_request_cookie | - | From "capture cookie"; a dash when not configured. |
| 10 | $14 | captured_response_cookie | - | Same, for the response. |
| 11 | $15 | termination_state | SD-- | Who ended the stream, in which phase, and what happened to the persistence cookie. |
| 12 | $16 | actconn/feconn/beconn/srv_conn/retries | 315/301/44/15/0 | Concurrent connections on the process, frontend, backend and server when logged, then retries. |
| 13 | $17 | srv_queue/backend_queue | 0/0 | How many requests were ahead of this one in the server queue and the backend queue. |
| 14β15 | β | {captured headers} | (absent) | From "capture request/response header". The braces only appear when capture is configured, and the content can contain spaces. |
| 16 | last | "http_request" | "GET /api/export HTTP/1.1" | Request line, always last and always quoted. Can be truncated if the request is longer than the syslog buffer. |
Reading the example: the server answered headers after 120 ms (Tr), then the connection to the server died with an error while the body was flowing (SD). The client got a 200 with 64 KiB of a response that was meant to be larger, 5032 ms after the request arrived. The status says success; the termination state says the download broke.
When the awk positions shift
The positions above assume the classic syslog header (Mmm dd hh:mm:ss host). An RFC 5424 or ISO-8601 header has a different number of words in front, and so does a log forwarded with an extra hostname. Captured headers only move fields after the queue counters. When in doubt, find the timer field (five numbers separated by slashes) and count from there; the one-liner below that finds the timer field does exactly that. The board does not care: it finds the client address and the request date and reads from there, whatever the header.
The five timers: TR / Tw / Tc / Tr / Ta
All values are milliseconds. They follow one request through the proxy:
first byte of the request last byte of the response
| |
|-- TR --|-- Tw --|-- Tc --|-------- Tr --------|---------- Td ----------|
request queue TCP server works until response body goes
headers wait connect response headers out to the client
|<------------------------------- Ta ------------------------------------>|
Before TR: Th (TCP accept + TLS / PROXY handshakes) and Ti (idle, keep-alive)
Tq = Th + Ti + TR Tt = Th + Ti + Ta Td = Ta - (TR + Tw + Tc + Tr)| Timer | What it measures | -1 means | Large values usually mean |
|---|---|---|---|
| TR | Time to receive the full request headers, from the first byte to the empty line ending them. | End of headers never seen: client closed early or timed out. | Network trouble between client and HAProxy, or a request typed by hand. |
| Tw | Time spent in the backend and server queues waiting for a connection slot. | Request killed before reaching the queue: invalid or denied. | Servers at their maxconn; requests wait their turn. |
| Tc | Time to establish the TCP connection to the server (SYN to SYN/ACK). | The connection to the server never established. | Network trouble between HAProxy and the server; should be about 1 ms on a LAN. |
| Tr | Server response time: from connection established until the server sent complete response headers. | Response headers never seen, most often because "timeout server" struck. | The server is slow on this request. For POST, includes the time the client took to send the body. |
| Ta | Total active time: first byte of the request to last byte of the response. | Never -1. | Slow transfer, a slow client, or a stream that hit a timeout. A "+" prefix means option logasap logged it early. |
Timer patterns that point at the cause
| Pattern | What happened | Check |
|---|---|---|
-1/xx/xx/xx/Ta | The client did not send a complete request in time, or aborted. | Termination state, then "timeout http-request" and "timeout client". |
TR/-1/xx/xx/Ta | The request could not be processed: servers down, invalid request, or denied by an ACL. | Termination state. |
TR/Tw/-1/xx/Ta | Connection to the server failed: refused, or timed out after Ta-(TR+Tw) ms. | Termination state, then "timeout connect". |
TR/Tw/Tc/-1/Ta | The server accepted the connection but did not return a full response in time, or closed after Ta-(TR+Tw+Tc) ms. | Termination state, then "timeout server". |
| values near 3000, 6000, 9000 | TCP retransmissions: TCP retransmit delays are 3, 6, 12β¦ seconds, so a timer close to a multiple of 3 s is almost always a lost packet. | Th/Ti near 3000: client side. Tc near 3000: HAProxy to server. |
| Ta close to a configured timeout | The stream was probably aborted by that timeout. | Which timeout matches, and the termination state. |
Th, Ti, Td, Tt, Tq, Tu
Th: TCP accept plus TLS and PROXY protocol handshakes. Reported only on the first request of a connection; later requests show 0.Ti: idle time before the request β time a pre-connected or keep-alive connection sat waiting. -1 if nothing was received.Td: transfer time of the response body, after the headers. Not in the default format; compute it as Ta β (TR + Tw + Tc + Tr), leaving out timers that are -1.Tt: whole stream duration, accept to close. For HTTP, Tt = Th + Ti + Ta.Tq: Th + Ti + TR. Older HAProxy versions loggedTq/Tw/Tc/Tr/Ttin the same position. Because Tq and Tt include handshake and idle time, they look much larger on keep-alive connections; the manual recommends TR over Tq.Tu: estimated time seen by the user β the stream duration without the idle time Ti.
Termination state: the four letters
Field 11 is two characters in TCP logs and four in HTTP logs. The first two say how the stream ended; the last two describe the persistence cookie, and are -- when cookies are not used. So the SD in a search for "SD--" is the part that matters, and cD-- is cD with no cookie involved.
First character: what ended the stream
| Char | Meaning |
|---|---|
C | The client aborted the TCP session unexpectedly. |
S | The server aborted the TCP session unexpectedly, or explicitly refused it. |
P | The proxy aborted the stream prematurely: connection limit, a deny rule, or a security check on the response. |
L | The stream was handled locally by HAProxy (redirect, return, stats, cacheβ¦). |
R | A proxy resource ran out: memory, sockets, source ports. Treat as a serious anomaly. |
I | Internal error found by a self-check. Should never happen; the manual asks for it to be reported as a bug. |
D | Killed because the server was detected down and is configured to kill connections when going down. |
U | Killed on a backup server because an active server came back up and backup connections are configured to be killed. |
K | Killed by an administrator. |
c | Client-side timeout expired while waiting for the client to send or receive data. |
s | Server-side timeout expired while waiting for the server to send or receive data. |
- | Normal completion: both sides closed with nothing left in the buffers. |
Second character: the phase it was in
| Char | Phase |
|---|---|
R | Waiting for a complete, valid request from the client (HTTP only). Nothing was sent to a server. |
Q | Waiting in the queue for a connection slot (servers with maxconn). Without a redispatch, no connection attempt was made. |
C | Waiting for the connection to the server to establish. |
H | Waiting for complete, valid response headers from the server (HTTP only). |
D | Data phase. |
L | Sending the last data to the client after the server had finished. Rare. |
T | The request was tarpitted; the time held open is reported in Tw. |
- | Normal completion after the end of the transfer. |
Third and fourth characters: the persistence cookie
| Char | Third: cookie from the client | Fourth: what happened to the cookie in the response |
|---|---|---|
N | No cookie provided. | No cookie from the server, none inserted. |
I | Invalid cookie, matching no known server. | No cookie from the server; HAProxy inserted one. |
D | Cookie pointing at a server that was down. | The server's cookie was deleted by HAProxy. |
V | Valid cookie; sent to that server. | β |
E | Valid cookie, but expired by "maxidle"; ignored. | β |
O | Valid cookie, but too old for "maxlife"; ignored. | β |
U | Cookie present but not used (for example a use-server rule chose). | HAProxy updated the last-seen date in the cookie (insert mode with maxidle). |
P | β | Cookie provided by the server and passed as is. |
R | β | Cookie provided by the server and rewritten by HAProxy. |
- | Does not apply (no cookie in the configuration). | Does not apply. |
Termination state reference: common combinations
The combinations the manual lists, what they mean in plain words, and where to look next. Uppercase is an abort or error, lowercase is a timeout.
| State | What happened | Check next |
|---|---|---|
-- | Normal termination. | Nothing on the proxy side. A 5xx with -- came from the server itself β read the application log. |
CC | The client gave up while HAProxy was still connecting to the server. | Tc and the server's health: this happens when the server is recently dead or unchecked. |
CD | The client aborted during the data transfer. | Usually browsers and mobile clients going away. Many on one path: large downloads, or a middlebox cutting connections. |
cD | The client sent and acknowledged nothing for "timeout client" during the data phase. | Client-side network loss or clients leaving uncleanly. Compare Ta with timeout client. |
CH | The client aborted while waiting for the server to start responding. | Tr on these lines: the server was slow, or the user hit Stop. |
cH | "timeout client" struck while waiting for client data during a POST. | Oversized TCP MSS on PPPoE paths, or timeout client shorter than timeout server with a slow server. |
CQ | The client aborted while queued for a server slot. | Tw and the queue fields: servers saturated or one server slow. |
CR | The client aborted before sending a full request. Status likely 400. | Hand-typed requests, browser pre-connects, or an IDS killing connections. "option http-ignore-probes" hides zero-byte connections. |
cR | "timeout http-request" struck before a full request arrived. Status likely 408. | Browser pre-connect, MSS problems, slow clients. Same "option http-ignore-probes" trade-off. |
CT | The client aborted while tarpitted. | Make sure the tarpit rules are not catching valid requests. |
LC | Handled locally: redirect from a "redir" server parameter. | Expected behaviour. |
LR | Handled locally: redirect, http-request return, or an applet (stats, cache, Prometheus exporter, Lua). | Expected behaviour. |
LH | The response was handled locally: redirect or http-response return. | Expected behaviour. |
SC | The server, or a device in front of it, refused the TCP connection (RST or ICMP). Status likely 502 or 503. | Is the server process listening? Firewall rules? Look at retries in field 12. |
sC | "timeout connect" struck before the connection to the server completed. Status likely 503 or 504. | Network path to the server, the server's accept backlog, the timeout connect value. |
SD | The connection to the server died with an error during the data transfer: RST from the server or ICMP from a device on the way. | Server crash or network device at that timestamp. The response the client received is truncated. |
sD | The server sent and acknowledged nothing for "timeout server" during the data phase. | L4 devices in front of the server (firewalls, load balancers) with shorter idle timeouts; keep-alive sessions expiring first on HAProxy. |
SH | The server aborted before sending complete response headers, or crashed while processing. | Server logs: rare, so the logged requests often reproduce an application bug. |
sH | "timeout server" struck before the server returned response headers. The most common anomaly. | Slow application or saturated database. Tr is -1 and Ta is close to timeout server. Raising the timeout only hides it. |
sQ | The request waited too long in the queue and expired. | "timeout queue" and "timeout connect"; server maxconn; bursts often mean I/O or database congestion on the servers. |
PC | The proxy refused to connect to the server because the process socket limit was reached. | Global maxconn, and ulimit-n if it was forced by hand. |
PD | The proxy blocked an incorrectly formatted chunked message after the server sent its headers. | Invalid chunked encoding from the server (chunk sizes up to 2 GB β 1 are accepted). |
PH | The proxy blocked the server's response: invalid, incomplete, dangerous, or matched a filter. The client gets a 502. | Header names with forbidden characters. A failed response header rewrite gives a 500 instead (tune.maxrewrite). |
PR | The proxy blocked the client's request: invalid syntax (400) or a deny rule (403). | Which deny rule matched; a failed request header rewrite gives a 500. |
PT | The proxy blocked and tarpitted the request, then returned a 500. Nothing reached a server. | Tw shows how long the connection was held. |
RC | A local resource ran out (memory, sockets, source ports) before the server connection could be made. | System tuning; the error output says which resource was missing. |
Cookie combinations
| Last two | Meaning |
|---|---|
-- | Persistence cookie not enabled. |
NN | No cookie from the client, none inserted (for example insert mode with postonly on a GET). |
NI | No cookie from the client, one inserted β a first request in insert mode. Counting these counts new users. |
VN | Valid cookie from the client, none inserted. Most returning requests. |
VU | Valid cookie with an outdated last-visit date; an updated cookie was sent. |
II | Cookie for an unknown server; a valid one was inserted. Typical after removing a server from the configuration. |
EI | Cookie too old for maxidle; ignored and replaced. |
OI | Cookie too old for maxlife; ignored and replaced. |
DI | The cookie's server was down; a new server was chosen and advertised. |
VI | The cookie's server was not marked dead but could not be reached; redispatched and the new server advertised. |
Users complaining that they have to log in again? Look at II, DI, VI, EI and OI around that time: each one is a request that left its original server.
Connection counters, retries and queues
| Field | Meaning | What to look for |
|---|---|---|
| actconn | Concurrent connections on the whole process. | Close to 512 or 1024 while connection errors happen: the process is probably out of file descriptors. |
| feconn | Concurrent connections on the frontend. | Big jumps: backend congestion, sometimes an attack; or frontend maxconn reached. |
| beconn | Concurrent connections on the backend, including queued ones. | Estimates how many more servers the load needs. |
| srv_conn | Concurrent connections still active on the server. Never above the server maxconn. | Often at maxconn: maxconn too low or not enough servers. Only one server high: that server is slow. |
| retries | Connection retries for this stream. Normally 0. | Frequent: network trouble or a full backlog on the server. A "+" prefix means a redispatch happened, so the count does not belong to the logged server. |
| srv_queue | Requests processed before this one in the server queue. | Tw divided by it roughly estimates the server response time. |
| backend_queue | Requests processed before this one in the backend queue. | Divided by a server's maxconn, roughly the number of missing servers. |
What the board shows for HAProxy
- Termination states: counts per state, each with its plain-language meaning, abnormal ones first.
- Request anatomy: TR, Tw, Tc, Tr and the transfer time as stacked bars, so you see whether time goes to the queue, the connect or the server.
- Topology from logs: frontend β backend β server drawn from fields 4 and 5, line width by traffic, red where errors come from.
- Retries and queue waiting, per backend and server.
- Look first: for example "HAProxy SD 61 times β backend be_api, server api-3".
- The usual cards too: status codes, top paths, top clients, latency, timeline.
Shell one-liners for HAProxy logs
Tested against a sample option httplog file with the classic syslog header. With another header, add or subtract the same offset from every field number, or use the one that finds the timer field.
All termination states, most frequent first.
awk '{print $15}' haproxy.log | sort | uniq -c | sort -rnOnly the first two characters (ignore cookie flags).
awk '{print substr($15, 1, 2)}' haproxy.log | sort | uniq -c | sort -rnSame, without trusting positions: finds the timer field and takes the fifth field after it.
awk '{for (i = 1; i <= NF; i++) if ($i ~ /^-?[0-9]+\/-?[0-9]+\/-?[0-9]+\/-?[0-9]+\/\+?[0-9]+$/) {print $(i+5); next}}' haproxy.log | sort | uniq -c | sort -rnAbnormal terminations per backend/server.
awk 'substr($15, 1, 1) != "-" {print substr($15, 1, 2), $9}' haproxy.log | sort | uniq -c | sort -rnStatus codes per backend.
awk '{split($9, b, "/"); print b[1], $11}' haproxy.log | sort | uniq -c | sort -rnRequests where the server took over a second to send headers (Tr), with the state.
awk '{split($10, t, "/"); if (t[4] > 1000) print t[4], $9, $15}' haproxy.log | sort -rnRequests that waited in a queue (Tw > 0), with the queue positions.
awk '{split($10, t, "/"); if (t[2] > 0) print t[2], $9, $17}' haproxy.log | sort -rnRequests where the connection to the server never established (Tc = -1).
awk '{split($10, t, "/"); if (t[3] == -1 && t[2] != -1) print $9, $11, $15}' haproxy.log | sort | uniq -cRequests with retries; a "+" means redispatched.
awk '{split($16, c, "/"); if (c[5] != "0") print c[5], $9, $15}' haproxy.logRequests that never reached a server.
grep -c '/<NOSRV> ' haproxy.logp99 of Ta, total active time.
awk '{split($10, t, "/"); sub(/^\+/, "", t[5]); print t[5]}' haproxy.log | sort -n | awk '{a[NR]=$1} END {i=int(NR*0.99); if (i<NR*0.99) i++; print a[i] " ms"}'Highest process-wide connection count seen.
awk '{split($16, c, "/"); if (c[1] > m) m = c[1]} END {print "peak actconn", m}' haproxy.logLongest body transfer times (Td = Ta β TR β Tw β Tc β Tr).
awk '{split($10, t, "/"); if (t[1] >= 0 && t[2] >= 0 && t[3] >= 0 && t[4] >= 0) print t[5]-(t[1]+t[2]+t[3]+t[4]), $9}' haproxy.log | sort -rn | head -3
FAQ
What does SD-- mean in HAProxy logs?
The connection to the server died with an error during the data transfer: HAProxy received an RST from the server or an ICMP message from a device in between. The status may still be 200, but the client got a truncated response. Check the server and the network path at that timestamp.
What does cD-- mean?
The client-side timeout expired during the data phase: the client neither sent nor acknowledged data for as long as "timeout client". Usually client network loss or a client that went away without closing.
What does sH-- mean?
"timeout server" expired before the server sent its response headers. It is the most common anomaly and points at a slow application or database. On these lines Tr is -1 and Ta is close to the timeout server value.
What do CR-- and cR-- mean?
Both are about a request that never fully arrived. CR: the client aborted first, status likely 400. cR: "timeout http-request" struck first, status likely 408. Browser pre-connects cause many of them; "option http-ignore-probes" stops logging zero-byte connections.
What do TR, Tw, Tc, Tr and Ta mean?
TR: time to receive the request headers. Tw: time in queues. Tc: time to connect to the server. Tr: time until the server sent its response headers. Ta: total active time from the first request byte to the last response byte. All in milliseconds.
Why is a timer -1?
That phase never completed. -1 in Tc means the server connection never established; -1 in Tr means the response headers never came; -1 in TR means the request headers never completed. Ta is never -1.
What does a + in front of the retries count mean?
The stream was redispatched to another server after the maximum retries on the first one. The retries then belong to the first server, not the one shown in the log.
What is <NOSRV>?
The request ended before any server was chosen or reached: rejected, redirected, handled locally, or the client gave up early. The termination state says which.
My logs show Tq/Tw/Tc/Tr/Tt. Is that the same?
That is the layout of older HAProxy versions. Tq includes the handshake and idle time, and Tt is the whole stream duration, so on keep-alive connections they are larger than TR and Ta. The board parses the old layout too but shows the values under the new names, so read its TR as Tq and its Ta as Tt for those files.