BytePane

HTTP Status Codes: Complete Reference Guide

HTTP16 min read

What Are HTTP Status Codes?

HTTP status codes are three-digit numbers that a server returns in response to every HTTP request. They tell the client (browser, API consumer, or crawler) what happened: whether the request succeeded, was redirected, encountered an error, or requires additional action. Every web developer, API designer, and DevOps engineer needs to understand these codes to build reliable systems and debug problems efficiently.

Status codes are defined in RFC 9110 (HTTP Semantics) and organized into five classes based on the first digit. The 1xx class is informational, 2xx indicates success, 3xx means redirection, 4xx signals a client error, and 5xx indicates a server error. This classification makes it possible to understand the general meaning of any status code even if you have never encountered the specific number before.

For a quick interactive lookup of any status code, use our HTTP Status Codes reference tool. It shows the code, name, description, and common causes for every standard status code.

1xx Informational Responses

The 1xx status codes are interim responses. They indicate that the server has received the request and the client should continue or wait. Most developers rarely encounter these directly because browsers and HTTP libraries handle them automatically.

CodeNameWhen It Occurs
100ContinueServer received headers, client should send body
101Switching ProtocolsUpgrading from HTTP to WebSocket
103Early HintsPreload resources before final response
// 100 Continue - useful for large uploads
// Client sends: Expect: 100-continue header
// Server responds with 100 if ready, or 413 if payload too large

// 101 Switching Protocols - WebSocket upgrade
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade

// Server responds:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade

2xx Success Codes

The 2xx status codes indicate that the request was received, understood, and processed successfully. These are the codes you want to see. Each code communicates a slightly different kind of success, which is important for API design.

CodeNameWhen to Use
200OKStandard success for GET, PUT, PATCH, DELETE
201CreatedResource created successfully (POST)
202AcceptedRequest accepted for async processing
204No ContentSuccess with no response body (DELETE)
206Partial ContentRange request (file downloads, video streaming)
// 200 OK - Return data
app.get('/api/users/:id', (req, res) => {
  const user = db.findUser(req.params.id);
  res.status(200).json(user);
});

// 201 Created - Return new resource with Location header
app.post('/api/users', (req, res) => {
  const newUser = db.createUser(req.body);
  res.status(201)
    .header('Location', `/api/users/${newUser.id}`)
    .json(newUser);
});

// 202 Accepted - Async job queued
app.post('/api/reports/generate', (req, res) => {
  const jobId = queue.enqueue('generate-report', req.body);
  res.status(202).json({ jobId, status: 'processing' });
});

// 204 No Content - Successful deletion
app.delete('/api/users/:id', (req, res) => {
  db.deleteUser(req.params.id);
  res.status(204).send();
});

A common API design mistake is returning 200 for everything. Using the correct 2xx code makes your API self-documenting and helps clients handle responses correctly. For example, a 201 tells the client it should check the Location header for the new resource URL, and a 204 tells it not to expect a response body.

3xx Redirection Codes

The 3xx status codes indicate that the client must take additional action to complete the request, usually by following a URL in the Location header. Choosing the correct redirect code is critical for SEO and user experience.

CodeNamePermanent?Use Case
301Moved PermanentlyYesDomain migration, URL restructuring
302FoundNoTemporary redirect (legacy)
303See OtherNoRedirect after POST (PRG pattern)
304Not ModifiedN/ACache validation (ETag/Last-Modified)
307Temporary RedirectNoTemporary redirect (preserves method)
308Permanent RedirectYesPermanent redirect (preserves method)
# Nginx - 301 permanent redirect (old URL to new URL)
server {
    listen 80;
    server_name old-domain.com;
    return 301 https://new-domain.com$request_uri;
}

# Nginx - 302 temporary redirect (maintenance page)
location /api/ {
    return 302 /maintenance.html;
}

# Express.js - 303 See Other (Post-Redirect-Get pattern)
app.post('/api/orders', (req, res) => {
  const order = db.createOrder(req.body);
  // After POST, redirect to GET the new order
  res.redirect(303, `/orders/${order.id}`);
});

SEO tip: always use 301 for permanent URL changes. Search engines transfer link equity (ranking power) from the old URL to the new one. A 302 does not transfer link equity, so using it for permanent moves wastes your SEO authority. The difference between 301/308 and 302/307 is whether the HTTP method is preserved -- 307/308 guarantee the method stays the same (important for POST redirects).

4xx Client Error Codes

The 4xx status codes mean the request contains an error on the client side. The server understood the request but refuses to process it because something is wrong with what the client sent. These codes should always include a descriptive error message in the response body to help the client fix the problem.

CodeNameCommon Cause
400Bad RequestMalformed syntax, invalid parameters
401UnauthorizedMissing or invalid authentication
403ForbiddenAuthenticated but lacks permission
404Not FoundResource does not exist at this URL
405Method Not AllowedWrong HTTP method (POST to GET-only endpoint)
409ConflictDuplicate resource, version conflict
413Payload Too LargeRequest body exceeds server limit
422Unprocessable EntityValid syntax but semantic errors (validation)
429Too Many RequestsRate limit exceeded

401 vs 403: The Most Confused Pair

These two codes are constantly confused. 401 Unauthorized actually means "unauthenticated" -- the server does not know who you are. Include a WWW-Authenticate header to tell the client how to authenticate. 403 Forbidden means the server knows who you are (you are authenticated) but you do not have permission to access this resource.

// 401 - No token provided or token is invalid
app.get('/api/admin', (req, res) => {
  if (!req.headers.authorization) {
    return res.status(401)
      .header('WWW-Authenticate', 'Bearer')
      .json({ error: 'Authentication required' });
  }
});

// 403 - Valid token but user is not an admin
app.get('/api/admin', authMiddleware, (req, res) => {
  if (req.user.role !== 'admin') {
    return res.status(403)
      .json({ error: 'Admin access required' });
  }
});

// 404 - Resource not found
app.get('/api/users/:id', (req, res) => {
  const user = db.findUser(req.params.id);
  if (!user) {
    return res.status(404)
      .json({ error: `User ${req.params.id} not found` });
  }
  res.json(user);
});

// 422 - Validation errors
app.post('/api/users', (req, res) => {
  const errors = validate(req.body);
  if (errors.length > 0) {
    return res.status(422)
      .json({ error: 'Validation failed', details: errors });
  }
});

// 429 - Rate limiting with Retry-After header
app.use(rateLimiter({
  max: 100,
  windowMs: 60000,
  handler: (req, res) => {
    res.status(429)
      .header('Retry-After', '60')
      .json({ error: 'Rate limit exceeded. Try again in 60 seconds.' });
  }
}));

5xx Server Error Codes

The 5xx status codes indicate that the server failed to fulfill a valid request. The problem is on the server side, not the client side. These codes should be treated as incidents that require investigation. In production, every 5xx response should be logged and monitored.

CodeNameCommon Cause
500Internal Server ErrorUnhandled exception, bug in code
502Bad GatewayUpstream server returned invalid response
503Service UnavailableServer overloaded or in maintenance
504Gateway TimeoutUpstream server did not respond in time
// 500 - Always use a global error handler
app.use((err, req, res, next) => {
  console.error('Unhandled error:', err);
  // Never expose stack traces to clients in production
  res.status(500).json({
    error: process.env.NODE_ENV === 'production'
      ? 'Internal server error'
      : err.message
  });
});

// 503 - Graceful maintenance mode
app.use((req, res, next) => {
  if (process.env.MAINTENANCE_MODE === 'true') {
    return res.status(503)
      .header('Retry-After', '3600')
      .json({ error: 'Service under maintenance' });
  }
  next();
});

502 vs 503 vs 504: Reverse Proxy Errors

These three codes are commonly returned by reverse proxies (Nginx, Cloudflare, AWS ALB) and are often confused. 502 Bad Gateway means the proxy received an invalid response from the upstream server -- usually because the application crashed or returned malformed data. 503 Service Unavailable means the upstream server is alive but not accepting requests -- it is overloaded, starting up, or in maintenance mode. 504 Gateway Timeout means the proxy waited for a response from the upstream server but it never came within the configured timeout period.

API Design: Choosing the Right Status Code

A well-designed API uses status codes consistently and predictably. Here is a decision guide for the most common API operations.

OperationSuccessNot FoundInvalid Input
GET /resource200404400
POST /resources201N/A422
PUT /resource/:id200404422
PATCH /resource/:id200404422
DELETE /resource/:id204404N/A
POST /resource/action200 or 202404400

When building APIs that return JSON, format your error responses consistently. Use our JSON Formatter to verify that error response structures are valid and readable. A standardized error format should include at minimum an error code, a human-readable message, and optionally a details array for validation errors.

// Consistent error response format
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      { "field": "email", "message": "Must be a valid email address" },
      { "field": "age", "message": "Must be a positive integer" }
    ]
  }
}

Troubleshooting Common HTTP Errors

When you encounter an HTTP error in production, here is a systematic approach to diagnosing and resolving the most common issues.

Debugging 404 Not Found

  1. Check the URL for typos, case sensitivity, and trailing slashes.
  2. Verify the resource exists on the server (check file system or database).
  3. Check your web server or router configuration for the correct route.
  4. Look for redirect rules that might be sending traffic to a wrong path.
  5. Check if the URL uses the correct encoding -- special characters need to be URL-encoded.

Debugging 500 Internal Server Error

  1. Check application logs for the stack trace and error message.
  2. Verify database connectivity and query correctness.
  3. Check that environment variables and configuration files are present.
  4. Look for recent deployments that may have introduced the bug.
  5. Check disk space, memory, and file descriptor limits.

Debugging 502/504 Gateway Errors

  1. Check that the upstream application is running (systemctl status myapp).
  2. Verify the proxy configuration points to the correct host and port.
  3. Check upstream application logs for crashes or resource exhaustion.
  4. Increase proxy timeout values if the upstream legitimately needs more time.
  5. Check network connectivity between the proxy and upstream server.
# Quick diagnosis commands
# Check if the upstream is running
curl -v http://localhost:3000/health

# View Nginx error log
tail -50 /var/log/nginx/error.log

# Check if the port is listening
ss -tlnp | grep 3000

# Test with verbose curl output
curl -v -H "Content-Type: application/json" https://api.example.com/users

# Follow redirects and show each hop
curl -L -v https://example.com/old-url 2>&1 | grep "< HTTP"

Common Misconceptions About Status Codes

Even experienced developers hold incorrect beliefs about HTTP status codes. Here are the most persistent misconceptions and the truth behind them.

  1. "401 means unauthorized" -- Despite its name, 401 actually means "unauthenticated." The client has not proven its identity. 403 is the code for "authenticated but not authorized."
  2. "Use 200 for everything and put the real status in the JSON body" -- This anti-pattern breaks HTTP caching, makes monitoring tools useless, and forces every client to parse the body before knowing if the request succeeded. Use proper status codes.
  3. "302 and 307 are the same" -- 302 allows the browser to change POST to GET on redirect (which most browsers do). 307 guarantees the method is preserved. Use 307 when you need to redirect a POST request and keep it as a POST.
  4. "404 means the server is down" -- A 404 means the server is working perfectly but the specific URL does not map to any resource. The server is up and responding -- it just does not have what you asked for.
  5. "5xx errors are always bugs" -- A 503 during planned maintenance or a 504 due to a slow third-party API are operational issues, not bugs in your code. Use 503 with a Retry-After header to communicate expected downtime.
  6. "You should never return 500" -- In practice, unhandled exceptions happen. The important thing is to catch them with a global error handler, log the details, and return a safe 500 response without exposing internal information.

HTTP Status Codes and Caching

Status codes play a critical role in HTTP caching. The browser and CDN caching behavior depends entirely on the status code returned, and getting this wrong can lead to stale content being served or fresh content never being cached.

CodeCacheable by Default?Notes
200YesCached according to Cache-Control headers
301YesBrowsers cache permanently (can be hard to undo)
302NoNot cached unless explicit Cache-Control set
304N/AInstructs client to use cached version
404YesBrowsers may cache 404s (use no-store to prevent)
500NoNever cached by default
// Proper caching headers for API responses
// Cacheable GET endpoint
app.get('/api/products', (req, res) => {
  res.set('Cache-Control', 'public, max-age=300'); // 5 min
  res.set('ETag', computeETag(products));
  res.status(200).json(products);
});

// Never cache sensitive data
app.get('/api/user/profile', auth, (req, res) => {
  res.set('Cache-Control', 'private, no-store');
  res.status(200).json(req.user);
});

// Conditional request handling (304)
app.get('/api/data', (req, res) => {
  const etag = computeETag(data);
  if (req.headers['if-none-match'] === etag) {
    return res.status(304).send(); // Use cached version
  }
  res.set('ETag', etag);
  res.status(200).json(data);
});

Frequently Asked Questions

What is the difference between 400 and 422?

Use 400 Bad Request when the request is syntactically malformed -- the server cannot even parse it (invalid JSON, missing required headers). Use 422 Unprocessable Entity when the syntax is valid but the content fails validation (email format wrong, number out of range). In practice, many APIs use 400 for both cases, but 422 is more precise for validation errors.

Should I use 404 or 204 for an empty search result?

Neither. Return 200 OK with an empty array. The resource (the search endpoint) exists and responded successfully -- it just found zero results. Use 404 only when the URL itself does not map to any resource. An empty result set is a successful response with no data, not a missing resource.

Do status codes affect SEO?

Absolutely. Search engines use status codes to decide how to index pages. 200 means the page exists and should be indexed. 301 transfers ranking authority to the new URL. 404 tells crawlers to drop the page from the index. 503 tells crawlers the page is temporarily unavailable and they should come back later. Using the wrong status code can cause pages to be incorrectly indexed or de-indexed.

What status code should rate limiters return?

Use 429 Too Many Requests with a Retry-After header that tells the client how many seconds to wait before retrying. Also include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers so clients can proactively manage their request rate.

Can I create custom HTTP status codes?

Technically yes, but it is strongly discouraged. HTTP clients may not understand non-standard codes. Some platforms use custom codes internally (Cloudflare uses 520-527 for edge errors, Nginx uses 444 for dropped connections), but these are non-standard and should not be used in your API responses. Stick to the standard codes defined in RFC 9110.

Look Up Any Status Code Instantly

Bookmark our HTTP Status Codes reference tool for instant lookups. Search by code number or category, see descriptions, common causes, and debugging tips for every standard HTTP status code.

Open HTTP Status Codes Reference

Related Articles