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.
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 stateSCorsQ. - Read the name in front of the slash. If it repeats the frontend name, no
use_backendrule matched and there is nodefault_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
Hostheader with a port.hdr(host) -i example.comdoes not matchexample.com:8080, so the rule fails and the request falls through to 503. Match withreq.hdr(host),host_onlyinstead. sQmeans the request waited for a free slot pasttimeout queue(ortimeout connectif that is unset). It comes from a servermaxconn. A frontendmaxconndoes not give a 503: in testing, the extra client just waited.- Every server DOWN or in MAINT gives
<NOSRV>and the alertbackend 'X' has no server available!. A server name that cannot be resolved stops HAProxy from starting, unlessinit-addr noneis 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/server | Timers TR/Tw/Tc/Tr/Ta | State | Cause | Alert in HAProxy's own log | Fix |
|---|---|---|---|---|---|
be_down/<NOSRV> | 0/-1/-1/-1/0 | SC-- | 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/0 | SC-- | 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/0 | SC-- | No use_backend rule matched, no default_backend | none | Add default_backend, or fix the rule |
fe_host/<NOSRV> (frontend name) | -1/-1/-1/-1/0 | SC-- | hdr(host) -i example.com failed on Host: example.com:8080 | none | req.hdr(host),host_only |
be_slow/<NOSRV> | 0/1008/-1/-1/1008 | sQ-- | Server maxconn full, waited past timeout queue | none | More servers or slots, or a faster app |
be_cookie/app1 | 0/1001/-1/-1/1001 | sQVN | Same, but a cookie pinned the request to one server | none | Same |
be/<NOSRV> | 0/-1/-1/-1/0 | SC-- | Server name not resolvable, started with init-addr none | none | Fix the name or the resolver |
be_nocheck/s1 | 0/0/-1/-1/3006 | SC-- | No health check; the server refused, 3 retries | none | Start 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) orsQwith 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:
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:
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:
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:
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:
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 sent | Rule | Result |
|---|---|---|
example.com | hdr(host) -i example.com | 200 |
example.com:8080 | hdr(host) -i example.com | 503, fe_host/<NOSRV> |
Example.COM:8080 | hdr(host) -i example.com | 503, fe_host/<NOSRV> |
example.com:8080 | req.hdr(host),host_only -i example.com | 200 |
EXAMPLE.com:8080 | req.hdr(host),host_only -i example.com | 200 |
example.com:8080 | hdr_dom(host) -i example.com | 200 |
www.example.com | hdr_dom(host) -i example.com | 200 |
example.com.evil.net | hdr_dom(host) -i example.com | 200 |
notexample.com | hdr_dom(host) -i example.com | 503 |
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:
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:
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:
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:
backend be
server app zp-late:8000 checkWith this, HAProxy 3.2.26 did not start at all:
[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:
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:
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:
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.
- 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--. - 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 forhas no server availablewould find nothing. - The
TRtimer is-1too, which matches the no-routing rows in the table above. - Open the frontend and read its
use_backendlines in order. Suppose there is nodefault_backendand the only rule isuse_backend be_web if { hdr(host) -i example.com }. - The partner's client sends
Host: 203.0.113.10:8080. That is neitherexample.comnor anything else the rule knows. No rule matches, so HAProxy answers 503 itself, as theuse_backendentry says it will. - 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.comso a port does not break it. If raw IP calls should never be served, add a last rule that answers them with a clear404or421instead 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.
- HAProxy 3.2 Configuration Manual, 8.2.3 HTTP log format (server_name, backend_name, Tw, Tc, retries)
- HAProxy 3.2 Configuration Manual, 8.5 Stream state at disconnection (SC, sQ)
- HAProxy 3.2 Configuration Manual, use_backend
- HAProxy 3.2 Configuration Manual, default_backend
- HAProxy 3.2 Configuration Manual, timeout queue
- HAProxy 3.2 Configuration Manual, server maxconn
- HAProxy 3.2 Configuration Manual, host_only converter
- HAProxy 3.2 Configuration Manual, 7.1.3 Matching strings (dom match)
- HAProxy 3.2 Configuration Manual, server init-addr
- HAProxy 3.2 Configuration Manual, retries
- HAProxy 3.2 Configuration Manual, 5.3 Server IP address resolution using DNS
- 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#
- Caddy 502 dial tcp connection refused: each cause
- ERR_TUNNEL_CONNECTION_FAILED: what the proxy answered
- Cntlm vs Px: which one for an NTLM or Kerberos proxy?
- Corporate proxies and developer tooling
- Access-Control-Allow-Origin multiple values: the fix
- curl through a proxy
- Envoy reset reason: connection termination, every cause
- Header and body size limits at the proxy
- HTTPS_PROXY: http:// or https://? Tested in 12 clients
- netsh winhttp vs Windows proxy settings: who reads which?
- nginx 413 Request Entity Too Large: find the limit
- nginx 499: what it means, and its ALB, HAProxy, Envoy names
- nginx 502 after Docker restart: fix the stale IP
- nginx in Docker: 502 on proxy_pass localhost, fixed
- nginx drops headers with underscores: why, and the fix
- nginx 400 plain HTTP request sent to HTTPS port: fix
- nginx proxy_cache always MISS: every cause, tested
- Nginx Proxy Manager 502 Bad Gateway: find your cause
- nginx (13: Permission denied) to upstream: SELinux fix
- nginx SSL handshaking to upstream errors: fix each one
- nginx cuts off large downloads: find which of 5 causes
- nginx upstream sent no valid HTTP/1.0 header: the fix
- nginx worker_connections: the right number for a proxy
- The no_proxy environment variable
- POST becomes GET after a redirect: proxies and clients
- A proxy debugging playbook
- Squid TCP_DENIED/403 on every request: the http_access fix
- Traefik upload fails after 60 seconds: the readTimeout fix