Troubleshooting

Why does HAProxy return 503 "No server is available to handle this request"?

HAProxy wrote that 503 itself and never reached a server. Tested on HAProxy 3.2.26: the log line for each cause, from all servers down to a Host port.

· 17 min read · How we verify this

Key points

  • HAProxy writes the page "No server is available to handle this request" itself. The request never reached a server. Look in the HAProxy log, not the app log: the line shows <NOSRV> in place of a server name, or a server name with state SC or sQ.
  • Read the name in front of the slash. If it repeats the frontend name, no use_backend rule matched and there is no default_backend. If it names a backend, a backend was chosen but had no server to give the request to.
  • The most-missed cause is a Host header with a port. hdr(host) -i example.com does not match example.com:8080, so the rule fails and the request falls through to 503. Match with req.hdr(host),host_only instead.
  • sQ means the request waited for a free slot past timeout queue (or timeout connect if that is unset). It comes from a server maxconn. A frontend maxconn does not give a 503: in testing, the extra client just waited.
  • Every server DOWN or in MAINT gives <NOSRV> and the alert backend 'X' has no server available!. A server name that cannot be resolved stops HAProxy from starting, unless init-addr none is set; then it gives the same 503 with no alert at all.

HAProxy returns 503 Service Unavailable with the body "No server is available to handle this request" when it answers the request itself, without ever reaching a server. Your app did not send that page, and its log will show nothing. The HAProxy log line tells you why. Where a server name would be, it shows <NOSRV>, or it shows a server name next to the state SC or sQ. The name in front of the slash then tells you which half of the problem you have: no backend was chosen, or a backend was chosen and had no server free.

Each row below was produced on HAProxy 3.2.26 with option httplog. The middle columns are copied from the real log lines:

Log shows backend/serverTimers TR/Tw/Tc/Tr/TaStateCauseAlert in HAProxy's own logFix
be_down/<NOSRV>0/-1/-1/-1/0SC--Every server failed its health check (DOWN)backend 'be_down' has no server available!Fix the servers, or the check
be_maint/<NOSRV>0/-1/-1/-1/0SC--Every server set to maintenance (MAINT)is going DOWN for maintenance, then has no server available!enable server on the runtime API
fe_nomatch/<NOSRV> (frontend name)-1/-1/-1/-1/0SC--No use_backend rule matched, no default_backendnoneAdd default_backend, or fix the rule
fe_host/<NOSRV> (frontend name)-1/-1/-1/-1/0SC--hdr(host) -i example.com failed on Host: example.com:8080nonereq.hdr(host),host_only
be_slow/<NOSRV>0/1008/-1/-1/1008sQ--Server maxconn full, waited past timeout queuenoneMore servers or slots, or a faster app
be_cookie/app10/1001/-1/-1/1001sQVNSame, but a cookie pinned the request to one servernoneSame
be/<NOSRV>0/-1/-1/-1/0SC--Server name not resolvable, started with init-addr nonenoneFix the name or the resolver
be_nocheck/s10/0/-1/-1/3006SC--No health check; the server refused, 3 retriesnoneStart the app, or fix the port

Each case is explained below, with the setup that produced it.

What NOSRV means#

Every request HAProxy logs with option httplog carries a backend_name/server_name field. The 3.2 manual, section 8.2.3, defines both halves. The server name is "the name of the last server to which the connection was sent". Then it adds: "If the request was aborted before reaching a server, <NOSRV> is indicated instead of a server name." So <NOSRV> does not mean "no backend". It means no server got the request.

The backend half settles which kind of failure it was. The same section says the backend name "will be the same as the frontend if no switching rule has been applied". So:

  • Frontend name, then /<NOSRV>. No backend was chosen. The routing rules are the problem.
  • Backend name, then /<NOSRV>. A backend was chosen. It had no usable server, or the request timed out in its queue.
  • Backend name, then a real server name, with a 503. A server was picked but the request never got an answer from it. In this article that means SC (the server refused) or sQ with a sticky cookie.

The four letters after the byte counts are the termination state. The first letter says what ended the session, the second says what phase it was in. SC-- reads "server side, connection phase". sQ-- reads "server-side timeout, queue phase". The full flag list is decoded in 502 vs 503 vs 504. This page only adds what those flags look like when no server was reached.

One detail there is easy to misread. The manual's text for SC describes a server that "explicitly refused the TCP connection". Every <NOSRV> case above also logged SC--, even though no connection was tried. The timers prove it: Tc, the time spent connecting, is -1 in each one. So SC next to <NOSRV> does not mean a server sent a reset. It means HAProxy gave up in the connect phase because it had nowhere to connect.

Some popular guides read <NOSRV> as "no backend was selected". That is true only when the frontend name sits in front of the slash. Four of the six <NOSRV> rows in the table have a backend name in front of the slash. Reading <NOSRV> alone sends you looking at ACLs when every server is down.

How the cases were reproduced#

Everything below was observed on 4 November 2026 unless it is attributed to the manual. HAProxy 3.2.26 ran from the official haproxy:3.2-alpine image on Docker 28.1.1, on one user-defined Docker network. Each case had its own frontend in one config, with mode http, option httplog, timeout connect 2s and logs to stdout. Two backends were Python 3.13 http.server containers, with a /slow path that slept four seconds before it answered. Requests came from curl in a third container. Servers were put into maintenance through the runtime API on a stats socket.

Case 1: every server is DOWN or in MAINT#

A backend with two servers on a port where nothing listened, both with check inter 1s. Within a second of start, HAProxy's own log said:

text
Server be_down/s2 is DOWN, reason: Layer4 connection problem, info: "Connection refused", check duration: 0ms. 0 active and 0 backup servers left. 0 sessions active, 0 requeued, 0 remaining in queue.
[ALERT]    (9) : backend 'be_down' has no server available!

Every request after that got the 503 at once:

text
10.201.14.5:40958 [04/Nov/2026:14:08:21.801] fe_down be_down/<NOSRV> 0/-1/-1/-1/0 503 217 - - SC-- 1/1/0/0/0 0/0 "GET / HTTP/1.1"

The second backend had two healthy servers. Both were then disabled with disable server be_maint/app1 and disable server be_maint/app2 on the runtime API. The log said Server be_maint/app2 is going DOWN for maintenance. 0 active and 0 backup servers left, then the same backend 'be_maint' has no server available! alert. The request log line was the same shape as the DOWN case. The stats page CSV (/stats;csv) told the two apart in its status column: DOWN for the failed servers, MAINT for the disabled ones, and DOWN for both backends.

So the alert is the quickest test. Search HAProxy's own log for has no server available. If it is there, the servers are the problem, not the routing. Then the reason: on the is DOWN line says why the check failed. If the app works when you call it yourself, compare that call with what the check sends: the path, and the Host header. Health checks and upstream failover covers how to design a check that does not take a working fleet down. HAProxy configuration for HTTP reverse proxying shows how to drain and re-enable a server on the runtime API.

A server set to MAINT on the runtime API stays there until someone enables it again with enable server. If the stats page shows MAINT and nobody remembers why, look for a deploy script that drains servers and never brings them back.

Case 2: no use_backend rule matched, and there is no default_backend#

A frontend with one rule and no fallback:

haproxy
frontend fe_nomatch
    bind :8002
    use_backend be_app if { path_beg /api/ }

GET /api/x got 200 from the app. GET / got the 503:

text
10.201.14.5:37062 [04/Nov/2026:14:08:22.803] fe_nomatch fe_nomatch/<NOSRV> -1/-1/-1/-1/0 503 217 - - SC-- 1/1/0/0/0 0/0 "GET / HTTP/1.1"

The manual's use_backend entry describes exactly this. Rules are "evaluated in their declaration order, and the first one which matches will assign the backend". If none match, default_backend is used. "If no default backend is defined, either the servers in the same section are used (in case of a listen section) or, in case of a frontend, no server is used and a 503 service unavailable response is returned."

Three things mark this case in the log. The frontend name is repeated after the frontend field. All five timers are -1 except the total. And there is no alert, because nothing is wrong with any backend. HAProxy has no way to warn you, since a frontend with no default is a valid config.

The fix depends on what you meant. If every request should go somewhere, add default_backend. If unmatched requests should fail, make them fail on purpose with a clearer status, for example http-request deny deny_status 404 as the last rule. Then a 503 means a real outage again, not a routing gap.

Case 3: the Host header carries a port#

This is the case most guides miss. The rule looks right and still fails:

haproxy
frontend fe_host
    bind :8003
    use_backend be_app if { hdr(host) -i example.com }

hdr(host) returns the Host header exactly as the client sent it. RFC 9110 defines that header as uri-host [ ":" port ], carrying "the host and port information from the target URI". The port is left out only when it is the scheme's default, so curl http://example.com:8080/ sends Host: example.com:8080. The -i flag makes the match ignore case, but it is still an exact string match, and example.com:8080 is not example.com. With no default_backend, the result is case 2 again:

Host sentRuleResult
example.comhdr(host) -i example.com200
example.com:8080hdr(host) -i example.com503, fe_host/<NOSRV>
Example.COM:8080hdr(host) -i example.com503, fe_host/<NOSRV>
example.com:8080req.hdr(host),host_only -i example.com200
EXAMPLE.com:8080req.hdr(host),host_only -i example.com200
example.com:8080hdr_dom(host) -i example.com200
www.example.comhdr_dom(host) -i example.com200
example.com.evil.nethdr_dom(host) -i example.com200
notexample.comhdr_dom(host) -i example.com503

The clean fix is the host_only converter. The manual says it "removes its port" from a Host header value. It accepts the forms hostname, hostname:80, 127.0.0.1:80 and [::1]:80, and "also sets the string in lowercase". So this rule matches with or without a port, in any case:

haproxy
use_backend be_app if { req.hdr(host),host_only -i example.com }

The older advice is hdr_dom(host). It also matched example.com:8080, but look at the last rows of that table. The manual defines the dom match as checking "that a dot-delimited portion of the contents exactly match one of the provided string patterns". So example.com matches any Host that contains those labels anywhere, and example.com.evil.net matched too. It also matched www.example.com, which may or may not be what you want. Use hdr_dom only when you mean "any name containing these labels". To route one exact name, use host_only.

So the port appears whenever the URL has one: HAProxy on 8080, a staging port, or a script that calls an IP and port directly. If one route works in the browser and fails from a script, compare the Host each one sends. The site's guide to Host, X-Forwarded-Host and X-Forwarded-Port explains which hop sets which value.

Case 4: the queue timed out (sQ)#

A server with maxconn 1 and timeout queue 1s, and two requests to /slow sent 0.3 seconds apart. The first took the only slot and got 200 after four seconds. The second waited for that slot, gave up after one second, and got the 503:

text
10.201.14.5:55898 [04/Nov/2026:14:09:01.354] fe_queue be_slow/<NOSRV> 0/1008/-1/-1/1008 503 217 - - sQ-- 2/2/1/0/0 0/1 "GET /slow HTTP/1.1"

The second timer, Tw, is the time spent in the queue: 1008 ms. The 0/1 at the end is "srv_queue / backend_queue", so the request waited in the backend's shared queue.

The manual explains both settings. A server's maxconn is "the maximal number of concurrent connections that will be sent to this server". In HTTP mode "this parameter limits the number of concurrent requests instead of the number of connections". Past that limit, requests "will be queued, waiting for a slot to be released". The default is 0, which means no limit, so sQ only happens where someone set a maxconn on the server. timeout queue is "the maximum time for a request to be left pending in a queue". When it fires, "it is considered that the request will almost never be served, so it is dropped and a 503 error is returned to the client".

There is a default worth knowing. If timeout queue is not set, "the same value as the backend's connection timeout (timeout connect) is used". The same test without a timeout queue line gave up after 2001 ms, which was the timeout connect 2s in defaults. A short connect timeout, a sensible thing on its own, quietly becomes a very short queue timeout.

When sQ shows a server name#

With a persistence cookie (cookie SRV insert indirect), the same test gave a different line:

text
10.201.14.6:35932 [04/Nov/2026:14:09:26.106] fe_cookie be_cookie/app1 0/1001/-1/-1/1001 503 217 - - sQVN 2/2/1/1/0 1/0 "GET /slow HTTP/1.1"

This time the server name is there, and the queue field reads 1/0: the request waited in app1's own queue. The manual's timeout queue entry says the queue "may be server-specific or global to the backend". A request pinned to a server by a cookie waits for that server only, so the log names it. The VN letters are about the cookie, not the error.

A frontend maxconn is not the same thing#

A maxconn 1 on the frontend, with the same two slow requests, gave no 503 at all. The second client waited and got 200 after 7.7 seconds. Its log line showed a four-second request with no queue time, stamped at the moment the first request finished. The wait happened before HAProxy took the connection, where the log cannot see it. So a full frontend shows up as slow responses that the log does not explain, not as sQ. Claims that frontend or global maxconn "rejects" requests with a 503 did not hold in this test.

The fix for real sQ lines is capacity, not a longer timeout. Each one means requests arrived faster than the servers finished them. Add servers, raise a maxconn that was set too low, or make the slow endpoint faster. A longer timeout queue only moves the failure later. Timeout budgets explains how the queue wait fits inside the timeouts of the hops in front of HAProxy.

Case 5: the server name could not be resolved at start#

A server line that names a host which does not exist yet:

haproxy
backend be
    server app zp-late:8000 check

With this, HAProxy 3.2.26 did not start at all:

text
[ALERT]    (8) : [/usr/local/etc/haproxy/haproxy.cfg:14] : 'server be/app' : could not resolve address 'zp-late'.
[ALERT]    (8) : Failed to initialize server(s) addr.

That is the default at work. The manual says init-addr "defaults to last,libc": try the server state file, then the system resolver. "If the end of the list is reached without finding a working method, an error is thrown."

The common workaround adds none, which the manual describes as starting "without any valid IP address in a down state". With init-addr none, HAProxy started and every request got the 503:

text
10.201.14.8:47652 [04/Nov/2026:14:09:56.374] fe be/<NOSRV> 0/-1/-1/-1/0 503 217 - - SC-- 1/1/0/0/0 0/0 "GET / HTTP/1.1"

Here is the trap. This instance logged no has no server available! alert, and no message about the name at all. Then the container named zp-late was started. With init-addr none alone, the 503s went on, because nothing told HAProxy to look the name up again. With a resolvers section pointing at Docker's DNS (nameserver dns 127.0.0.11:53) and resolvers docker on the server line, the log said:

text
be/app: IP changed from '(none)' to '10.201.14.8' by 'docker/dns'.
Server be/app administratively READY thanks to valid DNS answer.

The next request got 200. So init-addr none is only half a fix. On its own it trades a failed start for a silent 503 that lasts until the next reload. Pair it with resolvers, as section 5.3 of the manual describes, so HAProxy finds the server once the name exists.

A related case: a server name that resolves to the wrong address. localhost in a container can resolve to ::1 first, and HAProxy keeps only that one address. The result is a 503 with SC after three retries, shown in why nginx tries ::1 when proxy_pass says localhost.

Case 6: a named server with SC#

Not every one of these 503s has <NOSRV>. With no check on the server line, HAProxy does not know the server is down, so it picks it and tries to connect:

text
10.201.14.5:35744 [04/Nov/2026:14:08:32.478] fe_nocheck be_nocheck/s1 0/0/-1/-1/3006 503 217 - - SC-- 1/1/0/0/3 0/0 "GET / HTTP/1.1"

The page the client sees is the same 503. The log differs: a real server name, a total of about three seconds, and a 3 in the last counter of 1/1/0/0/3, which is the retries field. The manual's retries entry explains the timing: "a turn-around timer of min(timeout connect, one second) is applied before a retry occurs on the same server". Three retries, one second apart. The server refused because nothing listened on that port, so the fix is on the server side: start the app, or correct the port on the server line. This flag and its neighbours are covered in 502 vs 503 vs 504, so it is listed here only to complete the table. Adding check turns this into case 1: the 503 comes at once, with <NOSRV> and an alert.

Worked case: one 503 traced from its log line#

Take a site behind HAProxy that serves fine at https://example.com/. A partner calls the same site at http://203.0.113.10:8080/ and gets "No server is available to handle this request". The app log shows nothing for those calls. Walk it through.

  1. Find the HAProxy log line for one of the partner's requests. Suppose it reads fe_main fe_main/<NOSRV> -1/-1/-1/-1/0 503 217 - - SC--.
  2. The name in front of the slash is fe_main, the frontend. So no backend was chosen. The servers are not the problem, and searching for has no server available would find nothing.
  3. The TR timer is -1 too, which matches the no-routing rows in the table above.
  4. Open the frontend and read its use_backend lines in order. Suppose there is no default_backend and the only rule is use_backend be_web if { hdr(host) -i example.com }.
  5. The partner's client sends Host: 203.0.113.10:8080. That is neither example.com nor anything else the rule knows. No rule matches, so HAProxy answers 503 itself, as the use_backend entry says it will.
  6. Decide what the partner should get. If they should reach the site, they must call it by name, and the rule should be req.hdr(host),host_only -i example.com so a port does not break it. If raw IP calls should never be served, add a last rule that answers them with a clear 404 or 421 instead of a 503.

Had step 1 shown be_web/<NOSRV> instead, the path would split at step 2. Then you would search HAProxy's own log for has no server available, and check the stats page for DOWN or MAINT servers. Had it shown sQ-- with a large Tw, you would look at server maxconn and the app's response time instead.

Where this answer stops applying#

Everything above is HAProxy 3.2.26 in HTTP mode, with option httplog, on Docker on Linux. Other versions may word the alerts differently, so match on your own lines rather than this page. TCP mode was not tested. A custom errorfile 503 changes the page text but not the log line, so a branded error page can hide the words "No server is available" while the cause is the same. The custom error page guide covers that. Backup servers, option redispatch, balance first, server templates with SRV records and the Data Plane API were not tested, and each can change which server gets picked. A 503 that comes from your app, or from a CDN in front of HAProxy, is a different problem. In that case the HAProxy log names a real server, because the request reached one.

Frequently asked questions#

What does "No server is available to handle this request" mean in HAProxy?#

HAProxy answered the request itself with a 503, because it found no server to send it to. The request never reached your app. The HAProxy log line shows <NOSRV> where the server name would be, or a server name with the state SC or sQ.

What does NOSRV mean in the HAProxy log?#

<NOSRV> means the request was not sent to any server. The 3.2 manual says it appears "if the request was aborted before reaching a server". Look at the name in front of it. If that is the frontend's name, no backend was chosen; if it is a backend's name, that backend had no free or healthy server.

Why does HAProxy return 503 when the servers are up?#

The usual reasons are routing and queueing. If the log shows the frontend's name before /<NOSRV>, a use_backend rule did not match, often because the Host header carried a port. If it shows sQ, requests waited past timeout queue for a server maxconn slot.

Why does my hdr(host) ACL not match?#

hdr(host) returns the Host header exactly as sent, including any port. Host: example.com:8080 does not equal example.com, even with -i. Use req.hdr(host),host_only -i example.com, which strips the port and lowercases the value before the match.

What is the default timeout queue in HAProxy?#

There is no fixed default. If timeout queue is not set, HAProxy uses the backend's timeout connect value instead. With timeout connect 2s, a queued request in testing gave up with sQ after 2001 ms.

How do I find out why every HAProxy server is down?#

Search HAProxy's own log for has no server available, then read the is DOWN, reason: line above it. The reason names the check that failed, such as Layer4 connection problem with Connection refused. The stats page shows each server as DOWN, for failed checks, or MAINT, for servers disabled by hand.

Primary sources#

Every normative claim on this page is checked against the specification or the vendor documentation listed here. Where behaviour is version dependent, the version is named in the text.

  1. HAProxy 3.2 Configuration Manual, 8.2.3 HTTP log format (server_name, backend_name, Tw, Tc, retries)
  2. HAProxy 3.2 Configuration Manual, 8.5 Stream state at disconnection (SC, sQ)
  3. HAProxy 3.2 Configuration Manual, use_backend
  4. HAProxy 3.2 Configuration Manual, default_backend
  5. HAProxy 3.2 Configuration Manual, timeout queue
  6. HAProxy 3.2 Configuration Manual, server maxconn
  7. HAProxy 3.2 Configuration Manual, host_only converter
  8. HAProxy 3.2 Configuration Manual, 7.1.3 Matching strings (dom match)
  9. HAProxy 3.2 Configuration Manual, server init-addr
  10. HAProxy 3.2 Configuration Manual, retries
  11. HAProxy 3.2 Configuration Manual, 5.3 Server IP address resolution using DNS
  12. RFC 9110 HTTP Semantics, 7.2 Host and :authority

Found something wrong, or behaviour that differs on your version? Report it with the version number and a primary source. Anything substantive is fixed in the page and logged on the corrections page. See editorial standards for how pages are researched, sourced and reviewed.

More in troubleshooting proxies#