HTTP status codes explained: what each means for uptime

By the PULSX team. Updated . 16 minute read.

TL;DR

An HTTP status code is a three-digit number in every response. The first digit gives the class: 2xx worked, 3xx go elsewhere, 4xx the request was wrong, 5xx the server failed. For uptime, treat 5xx codes and missing responses as down, treat an expected 401 as healthy, and never trust a 200 without checking what the page says.

Key takeaways

  • The first digit is the class: 1xx information, 2xx success, 3xx redirect, 4xx client error, 5xx server error.
  • 502 means the proxy got a bad answer from your app, 503 means the server refuses work for now, 504 means the proxy waited too long.
  • Codes 520 to 526 come from Cloudflare only. They are not in the HTTP standard.
  • A 401 on a protected API is a healthy answer. A 200 that shows an error page is not.
  • A timeout, a refused connection or a failed TLS handshake has no status code at all, and all three mean down.
  • Use curl -o /dev/null -s -w "%{http_code}" to see the code a GET request really gets.

What is an HTTP status code?

An HTTP status code is a three-digit number that a server sends at the start of every response. It tells the client whether the request worked and, if it did not, which side is at fault.

The first digit is the class. There are five classes. They are defined in section 15 of RFC 9110, the current HTTP standard.

ClassNameWhat it meansExamples
1xxInformationalThe request arrived and the server is still working on it.100, 101
2xxSuccessfulThe request was received, understood and accepted.200, 201, 204
3xxRedirectionThe client must do something else, usually request another URL.301, 302, 304
4xxClient errorThe request was wrong: bad syntax, no permission, no such page.400, 401, 404
5xxServer errorThe request looked valid but the server failed to answer it.500, 502, 503

One rule covers most cases: 4xx means the request was wrong, 5xx means the server broke. That split matters for uptime, because a 5xx is almost always your problem and a 4xx often is not.

You can see the code on the first line of any response:

$ curl -I https://example.com
HTTP/2 200
content-type: text/html; charset=UTF-8

HTTP/1.1 adds a short text after the number, such as 200 OK. HTTP/2 and HTTP/3 send only the number.

Which status codes matter in practice?

A couple of dozen codes cover almost everything you will meet. The table lists them with a plain meaning and whether an uptime monitor should treat each one as down.

CodeNamePlain meaningDown?
200OKThe request worked and the body holds the result.No, if the body is the page you expect.
201CreatedThe request made a new resource. The usual answer to a successful POST.No.
204No ContentThe request worked and there is no body to send.No.
301Moved PermanentlyThe resource has a new URL for good. A client may change a POST into a GET.No, but check where it points.
302FoundThe resource is at another URL for now. A client may change a POST into a GET.No, but check where it points.
304Not ModifiedYour cached copy is still good. Sent only in answer to a conditional request.No.
307Temporary RedirectLike 302, but the client must keep the same method and body.No, but check where it points.
308Permanent RedirectLike 301, but the client must keep the same method and body.No, but check where it points.
400Bad RequestThe server could not read the request: bad syntax or invalid framing.Only if your monitor sends a correct request.
401UnauthorizedNo valid credentials were sent. The response carries a WWW-Authenticate header.No, on a protected endpoint.
403ForbiddenThe server understood the request and refuses it.No, on a protected endpoint. Yes, on a public page.
404Not FoundThe server has nothing at this URL.Yes, if the page should exist.
405Method Not AllowedThe URL exists but does not accept this method. The Allow header lists the ones it does.Usually a monitor setting to fix.
408Request TimeoutThe server gave up waiting for the client to finish sending the request.Rarely. Look at the network first.
410GoneThe resource was removed on purpose and is not coming back.Yes, if the page should exist.
422Unprocessable ContentThe syntax is fine but the content fails validation.No.
429Too Many RequestsThe client is being rate limited. Defined in RFC 6585.No, but your monitor is being limited.
499(nginx only)The client closed the connection before nginx could answer. It appears in nginx access logs and is never sent to a client.Not seen by a monitor.
500Internal Server ErrorThe server hit a condition it did not expect.Yes.
501Not ImplementedThe server does not support what the request needs, usually the method.Yes.
502Bad GatewayA proxy got an invalid answer from the server behind it.Yes.
503Service UnavailableThe server cannot take the request right now: overload or maintenance.Yes.
504Gateway TimeoutA proxy did not get an answer from the server behind it in time.Yes.
520 to 526(Cloudflare only)Cloudflare could not get a good answer from your origin server.Yes.

Every code in this table from 200 to 504, apart from 429 and 499, is defined in RFC 9110. Codes 499 and 520 to 526 are private codes made up by one product each. No standard defines them.

What does each 5xx code mean, and who sent it?

Every 5xx code says the server side failed, but each one points at a different layer. Knowing which layer wrote the response tells you where to look first.

Most sites have at least two layers: a proxy or load balancer in front (nginx, a CDN, a cloud load balancer) and your application behind it. The application is called the upstream or origin.

CodeWhat it meansWho usually sends itFirst thing to check
500The code that handles the request crashed or hit a state it did not expect.Your application.The application error log, at the time of the request. Look for a stack trace.
501The server does not support the method or feature the request needs.Your application or web server.The request method. A 501 often follows an unusual method that the server does not know.
502The proxy reached for your application and got nothing usable back.The proxy, load balancer or CDN.Whether the application process is running and listening on the port the proxy expects.
503The server is up but refuses work for now.The application, the proxy, or a load balancer.Maintenance mode, rate limits, and whether the server is out of workers, memory or connections.
504The proxy connected to your application and waited, and no answer came in time.The proxy, load balancer or CDN.What the application is slow on: a database query, a lock, a call to another service.

How to tell which layer answered

Look at the response headers and body. A default nginx error page says "nginx" in the body. A response that passed through Cloudflare carries a cf-ray header and server: cloudflare.

Then compare logs. If the proxy logged a 502 and your application log shows no request at that second, the request never reached the application. If the application logged a 500, the proxy only passed it along.

What is the difference between 502, 503 and 504?

A 502 means the proxy got a bad answer or no answer from your application. A 503 means a server chose to refuse the request for now. A 504 means the proxy waited for your application and ran out of time.

A worked example with nginx

Take a common setup: nginx on port 80, passing requests to an application on port 3000 of the same machine.

upstream app {
    server 127.0.0.1:3000;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://app;
        proxy_connect_timeout 5s;
        proxy_read_timeout 60s;
    }
}

Case 1: the application has stopped. nginx tries to open a connection to 127.0.0.1:3000. Nothing is listening, so the operating system refuses it at once. nginx returns 502 and writes this to its error log:

connect() failed (111: Connection refused) while connecting to upstream

Case 2: the application crashes halfway through a request. nginx connected, sent the request, and the connection closed before any response arrived. nginx returns 502 again, with a different log line:

upstream prematurely closed connection while reading response header from upstream

Case 3: the application is running but one request takes 90 seconds. nginx connects and waits. With proxy_read_timeout 60s, it gives up after 60 seconds without a byte from the application and returns 504:

upstream timed out (110: Connection timed out) while reading response header from upstream

Case 4: a 503. nginx does not turn a dead or slow application into a 503. You see a 503 from this setup in three situations:

  • The application itself answers 503 and nginx passes it on.
  • You switched on a maintenance block with return 503;.
  • A rate limit set with limit_req or limit_conn rejected the request. Both use 503 unless you change it.

Other proxies and load balancers draw the line in other places. Many load balancers return 503 when they have no healthy server to send traffic to. Read the documentation for the one you run.

What to run for each

# 502: is the application up, and does it answer locally?
systemctl status myapp
curl -I http://127.0.0.1:3000/

# 504: how long does the application take without the proxy?
curl -o /dev/null -s -w "%{time_total}\n" http://127.0.0.1:3000/slow-page

# All three: what did nginx say?
tail -n 20 /var/log/nginx/error.log

Raising proxy_read_timeout makes a 504 go away without fixing anything. The request is still slow. Find the slow query or the slow outside call first.

What are Cloudflare errors 520 to 526?

Codes 520 to 526 are sent only by Cloudflare. Each one means Cloudflare received the visitor's request but could not get a proper response from your origin server. They are not part of the HTTP standard and no other server uses them with these meanings.

If your site is not behind Cloudflare, you will never see them. If it is, they replace the plain 502 and 504 you would see with your own proxy, and they are more specific.

CodeCloudflare's nameWhat happenedFirst thing to check
520Web server returned an unknown errorThe origin sent something Cloudflare could not use: an empty response, a reset connection, or a malformed one.Origin error logs and application crashes at that time.
521Web server is downThe origin refused the connection from Cloudflare.Whether the web server is running, and whether a firewall blocks Cloudflare's addresses.
522Connection timed outCloudflare could not complete a TCP connection to the origin.Firewall rules that drop packets, an overloaded origin, a wrong IP address in the DNS record.
523Origin is unreachableCloudflare could not find a network route to the origin address.The origin IP address in your DNS records.
524A timeout occurredCloudflare connected, but the origin did not send an HTTP response in time.Slow requests in the application. This is the Cloudflare form of a 504.
525SSL handshake failedThe TLS handshake between Cloudflare and the origin failed.That the origin serves TLS on port 443 with a certificate installed.
526Invalid SSL certificateCloudflare could not validate the origin's certificate.The origin certificate: expired, self-signed or for the wrong hostname.

For 525 and 526 the certificate at fault is the one on your origin, not the one visitors see. You can inspect any public certificate with the SSL checker, and the guide to certificate expiry explains how to stop one lapsing.

A monitor that requests your public URL sees these codes exactly as a visitor does. Treat all seven as down.

Which status codes should count as down?

Count a check as down when the answer is a 5xx, when the answer is a code the URL should never give, or when there is no answer at all. Do not count a code as down only because it is not 200.

The right question is: did the server give the answer this URL is supposed to give?

Count these as down

  • Any 5xx, including Cloudflare's 520 to 526. The server side failed.
  • No response. A DNS failure, a refused connection, a timeout or a failed TLS handshake produces no status code. These are the most complete outages there are.
  • A 404 or 410 on a page that should exist. The server is running, but a deploy or a routing change lost the page. For the visitor it is down.
  • A 403 on a public page. Something is blocking normal visitors: a file permission, a firewall rule, a geo block.

Do not count these as down

  • A 401 on a protected API. If you request https://api.example.com/v1/orders with no token, 401 is the correct answer. The server is up, it parsed the request and it enforced its rules. That is a healthy system.
  • A 301, 302, 307 or 308 you expect. A redirect from http:// to https:// is normal. Follow it and judge the final answer.
  • A 429. The server is limiting your monitor. Visitors are fine. Fix it by allowing the monitor's requests, not by ignoring the alert.
  • A 405. Your monitor uses a method the endpoint does not accept, often HEAD or GET on a POST-only route. Change the monitor.

The awkward middle

A 403 or 429 can also come from a firewall or bot filter that mistakes your monitor for an attacker. Then the monitor reports an outage that no visitor sees. If a 403 alert arrives and the site loads fine in a browser, check the firewall logs for the monitor's requests.

A planned 503 during maintenance is down from the visitor's side, but nobody needs waking for it. Schedule a maintenance window in your monitoring tool so the planned work does not page anyone.

Can a 200 response still mean the site is broken?

Yes. A 200 only says the server sent a response it considers successful. It says nothing about what is on the page. A site can return 200 for every request while showing every visitor an error.

Common ways this happens:

  • The application catches a database error and renders a friendly "Something went wrong" page without setting the status to 500.
  • A single-page app serves the same index.html with 200 for every path, then fails to load its data in the browser.
  • A hosting account is suspended, or a domain is parked, and the placeholder page returns 200.
  • A maintenance page is served with 200 in place of 503.

What is a soft 404?

A soft 404 is a page that tells the reader "not found" while the server returns 200. The words say one thing and the status code says another.

It causes three problems. Search engines may index the empty page. Caches store it as a good response. Monitors and link checkers think the URL is fine. Test your own site with a path that cannot exist:

$ curl -o /dev/null -s -w "%{http_code}\n" https://example.com/no-such-page-8472
200

If that prints 200, you have soft 404s. A correct site prints 404.

The fix: make the status code tell the truth

Fix this in the application, not in the monitor. Error pages must be sent with a 4xx or 5xx status. Then every client gets the right signal: browsers, search engines, caches and monitors.

For monitoring, add a health endpoint that tests what the site depends on and answers with an honest code:

app.get('/health', async (req, res) => {
  try {
    await db.query('SELECT 1');
    res.status(200).json({ status: 'ok' });
  } catch (err) {
    res.status(503).json({ status: 'database unreachable' });
  }
});

Point your monitor at /health as well as the home page. The home page check proves visitors can load the site. The health check proves the database behind it answers.

What does the Retry-After header do?

Retry-After is a response header that tells the client how long to wait before it tries again. It is defined in section 10.2.3 of RFC 9110.

The value takes one of two forms: a number of seconds, or a date.

HTTP/1.1 503 Service Unavailable
Retry-After: 120

HTTP/1.1 429 Too Many Requests
Retry-After: Wed, 14 Oct 2026 07:28:00 GMT

You will meet it with three kinds of response:

  • 503: how long the service expects to be unavailable.
  • 429: how long until the rate limit lets you in again.
  • 3xx: the minimum time to wait before following the redirect.

The header is advice. A client can ignore it, but a well-behaved one does not. If you write a script or a job that calls an API, read Retry-After on a 429 or 503 and sleep for that long. Retrying at once makes an overloaded server worse.

Use it for planned maintenance

When you take a site down on purpose, send 503 with Retry-After. That tells crawlers and API clients the outage is temporary and when to come back. In nginx:

location / {
    add_header Retry-After 600 always;
    return 503;
}

The word always matters. Without it, nginx adds the header only to successful and redirect responses, and your 503 goes out without it.

How should a monitor handle redirects?

A monitor should follow redirects and judge the final response, and you should know in advance where each monitored URL ends up. A redirect is not a failure, but a redirect to the wrong place is.

The four redirect codes differ on two points: whether the move is permanent, and whether the client must keep the request method.

CodeLastsMethod kept?Typical use
301PermanentNot guaranteed. POST may become GET.Moving a page for good, http to https.
302TemporaryNot guaranteed. POST may become GET.Sending a visitor to a login page.
307TemporaryYes.A temporary move of an API endpoint.
308PermanentYes.A permanent move of an API endpoint.

Browsers cache a 301 or 308, often for a long time. A wrong permanent redirect is hard to take back, because returning visitors never ask the server again. Test with 302 or 307 first.

Redirect problems that hide outages

  • A redirect to a login page. A session or auth fault sends every visitor to /login, which returns 200. A monitor that follows redirects sees 200 and stays quiet.
  • A redirect to a parked or suspended page. The final page loads fine. It is the wrong page.
  • A loop. A redirects to B and B redirects to A. Browsers show a "too many redirects" error. curl with -L stops after 50 redirects.

Print the whole chain to see what really happens:

$ curl -sIL http://example.com | grep -i -E "^HTTP|^location"
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
HTTP/2 301
location: https://www.example.com/
HTTP/2 200

Two hops here: http to https, then the bare domain to www. Monitor the final URL, https://www.example.com/, so the check measures the page and not the hops. If the redirects matter to you, add a second monitor on the starting URL.

How do you see the status code of a URL?

Use curl. Two commands cover nearly every case: curl -I to print the response headers, and curl -o /dev/null -s -w "%{http_code}" to print only the code.

curl -I: headers only

$ curl -I https://example.com
HTTP/2 200
content-type: text/html; charset=UTF-8
cache-control: max-age=3600

-I sends a HEAD request, which asks for the headers without the body. It is fast and easy to read. The catch is that some applications answer HEAD differently from GET: a 405, or a 200 where a GET would fail.

curl -w: the code a real GET receives

$ curl -o /dev/null -s -w "%{http_code}\n" https://example.com
200

What each part does:

  • -o /dev/null throws the body away.
  • -s hides the progress meter.
  • -w "%{http_code}\n" prints the status code and a new line when the transfer ends.

Add -L to follow redirects, and more variables to see where you ended up and how long it took:

$ curl -o /dev/null -s -L \
    -w "code=%{http_code} redirects=%{num_redirects} total=%{time_total}s url=%{url_effective}\n" \
    http://example.com
code=200 redirects=2 total=0.412345s url=https://www.example.com/

When curl prints 000

A code of 000 means curl got no HTTP response at all. Run the command again without -s, or print the exit code with echo $?:

curl exit codeMeaning
6Could not resolve the hostname. A DNS problem.
7Could not connect. Often "connection refused".
28The operation timed out.
35The TLS handshake failed.
60The certificate could not be verified, for example because it expired.

If you only want a quick look from outside your own network, the website down checker requests the URL from a server and shows the result.

How do you turn status codes into useful alerts?

Decide the expected answer for each URL, check it on a schedule from outside your own network, and alert only when the wrong answer repeats. One stray 502 during a deploy is not an incident. Three in a row is.

A short plan that works for most sites:

  1. Monitor the final URL of your main page. Expect 200.
  2. Monitor a health endpoint that returns 503 when the database or another hard dependency fails.
  3. Retry before alerting. A single failed check can be a network blip between the monitor and you.
  4. Write the status code into the alert. "502 from example.com" sends you to the application process. "Timeout" sends you to the network or the firewall.

PULSX runs HTTP(S) checks as often as every 60 seconds. A failed check is retried 3 times, 30 seconds apart, before an alert goes out by email, Slack, Discord, webhook or push to the Android app. It is free for 20 monitors, with no card needed.

When an alert does fire, the code is your first clue and not the whole answer. The website down checklist walks through the layers in order, from DNS to the database. If you report uptime to customers, uptime and SLAs explained covers how failed checks turn into a percentage.

Common questions

What is the difference between a 502, a 503 and a 504 error?+
A 502 Bad Gateway means a proxy got an invalid answer, or no answer, from the application behind it, often because the application has stopped. A 503 Service Unavailable means a server is refusing requests for now because of overload, a rate limit or maintenance. A 504 Gateway Timeout means the proxy connected to the application and waited, and no response came before the timeout.
Does a 401 or 403 mean my site is down?+
Not on a protected endpoint. A 401 on an API route that needs a token is the correct answer and shows the server is running and enforcing its rules. A 403 on a page that the public should be able to read is a real problem, and a 403 seen only by your monitor usually means a firewall or bot filter is blocking the monitor.
Are Cloudflare 520 to 526 errors official HTTP status codes?+
No. Codes 520 to 526 are used only by Cloudflare and are not defined in RFC 9110 or any other HTTP standard. Each one means Cloudflare received the request but could not get a valid response from your origin server. For example, 521 means the origin refused the connection, 522 means the connection timed out and 526 means the origin certificate could not be validated.
What does status code 499 mean in nginx logs?+
It means the client closed the connection before nginx had sent a response. It is an nginx-only code that appears in the access log and is never sent to a browser. Many 499 lines usually point to a slow backend, because visitors and other clients give up waiting.
Should a maintenance page return 200 or 503?+
It should return 503, ideally with a Retry-After header that says how many seconds to wait. A 503 tells browsers, crawlers, caches and API clients that the outage is temporary. A maintenance page sent with 200 looks like a normal page, so it can be cached or indexed as if it were your real content.
What is a soft 404?+
A soft 404 is a page that tells the reader the content was not found while the server returns a 200 status code. It misleads search engines, caches and monitors, which all read the code and not the words. You can test for it by requesting a path that cannot exist and checking that the server answers 404.
How do I check the HTTP status code of a URL from the command line?+
Run curl -I followed by the URL to send a HEAD request and print the response headers, with the status code on the first line. To see the code a normal GET request receives, run curl -o /dev/null -s -w "%{http_code}" followed by the URL. Add -L to follow redirects. If curl prints 000, no HTTP response arrived at all.
What is the difference between a 301 and a 308 redirect?+
Both say the resource has moved for good. The difference is the request method. After a 301, a client is allowed to change a POST into a GET when it follows the redirect. After a 308, the client must repeat the request with the same method and body. Use 308 for permanent redirects of API endpoints that receive POST or PUT requests.

More guides

Free DNS, SSL and cron tools

Know the minute your site goes down.

PULSX checks your site, API, cron jobs and SSL certificate every 60 seconds. Free for 20 monitors, with no card.