When you’re working with APIs or websites, you’ll run into HTTP status codes all the time — 200, 404, 502, and so on. These codes tell you whether a request went through or where it broke. But knowing the code is only half the story. The other half is figuring out who on your team should actually fix it — is it a backend bug, a DevOps misconfiguration, or a client-side problem?
This post breaks down every major HTTP status code, explains what causes it, and maps it to the team member who should own the fix. I also included terminal commands you can use to diagnose each one.
How to Check HTTP Status Codes
From the Terminal
The quickest way to check a status code is with curl. The -I flag sends a HEAD request and shows you just the response headers:
curl -I https://example.com
Look for the line that says something like HTTP/1.1 200 OK or HTTP/2 301. If you want to isolate just the status line from a full response:
curl -vso /dev/null https://example.com 2>&1 | grep "< HTTP"
From the Browser
Open Developer Tools with F12, go to the Network tab, and reload the page. The Status column shows the HTTP status code for every request. This is useful when you need to inspect specific assets like API calls, images, or stylesheets.
1xx – Informational
| Code | Meaning | Who Fixes It |
|---|---|---|
| 100 | Continue | No action needed |
| 101 | Switching Protocols | No action needed |
You’ll rarely see these in your logs. They just mean the server acknowledged the request and processing is underway. Nothing to worry about here.
2xx – Success
| Code | Meaning | Who Fixes It |
|---|---|---|
| 200 | OK | No issue |
| 201 | Created | No issue |
| 204 | No Content | No issue |
200 is the standard “everything worked” response. 201 means a new resource was created — you’ll see this after successful POST requests. 204 means the request succeeded but there’s no response body, which is typical for DELETE operations.
3xx – Redirection
| Code | Meaning | Who Fixes It |
|---|---|---|
| 301 | Moved Permanently | Developer / SysAdmin |
| 302 | Found (Temporary Redirect) | Developer |
| 304 | Not Modified | Developer |
Redirects are normal — your server probably redirects HTTP to HTTPS, and that’s fine. The problem starts when redirects loop. A classic example: your Nginx config redirects HTTP to HTTPS, but your load balancer already handles SSL termination. The request bounces back and forth and the browser eventually gives up.
To check for redirect chains or loops:
curl -ILs https://example.com | grep -i "HTTP/"
The -L flag tells curl to follow redirects, and -s keeps the output clean. If you see more than 2-3 redirect hops, something is misconfigured. Check your web server and load balancer for conflicting redirect rules.
4xx – Client Errors
| Code | Meaning | Who Fixes It | Common Cause |
|---|---|---|---|
| 400 | Bad Request | Developer | Malformed JSON, missing required fields |
| 401 | Unauthorized | Developer / Security | Missing or expired auth token |
| 403 | Forbidden | Security / DevOps | File permissions, IP blocklist, WAF rules |
| 404 | Not Found | Developer | Wrong URL, deleted resource, broken route |
| 405 | Method Not Allowed | Developer | Sending POST to a GET-only endpoint |
| 408 | Request Timeout | Developer / DevOps | Slow client, large payload without streaming |
| 429 | Too Many Requests | Developer / DevOps | Rate limit exceeded |
4xx errors are technically “client errors,” meaning the request itself has a problem. But in practice, some of them — like 403 — are often caused by server-side misconfigurations.
400 Bad Request
This usually means the request body is malformed. Missing a required field, sending invalid JSON, wrong content type — that kind of thing. Test your API call with curl to narrow it down:
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "test"}' \
-w "\nHTTP Status: %{http_code}\n"
The -w flag appends the HTTP status code to the output so you can see the result right away without parsing headers.
401 Unauthorized
Your auth token is missing, expired, or just wrong. Verify it’s being sent correctly in the header:
curl -I -H "Authorization: Bearer YOUR_TOKEN" https://api.example.com/me
If the token looks right but you’re still getting 401, check if the token has expired or if the API expects a different auth scheme (API key vs Bearer token, for example).
403 Forbidden
This one is tricky because even though it’s classified as a client error, it’s often a server-side issue. The server understood your request but refuses to fulfill it. Common causes: file permission issues, IP blocklists, or WAF rules blocking the request.
If you’re running a web server like Apache or Nginx, check the file permissions first:
ls -la /var/www/html/
namei -l /var/www/html/index.html
namei -l walks the full path and shows permissions at each directory level. This helps you find exactly where access gets blocked. Make sure the Nginx worker process user (www-data on Ubuntu) has read access to all directories in the path.
429 Too Many Requests
You’ve hit a rate limit. The response headers usually tell you how long to wait before retrying:
curl -I https://api.example.com/endpoint 2>&1 | grep -i "rate\|retry\|limit"
Look for Retry-After, X-RateLimit-Remaining, or X-RateLimit-Limit headers. If you’re calling APIs from a Lambda function or automated script, you’ll want to add retry logic with exponential backoff. I wrote about handling this pattern in How to Make Reliable HubSpot API Requests in Python (With Retry Logic) — the same approach works for any API.
5xx – Server Errors
| Code | Meaning | Who Fixes It | Common Cause |
|---|---|---|---|
| 500 | Internal Server Error | Backend / DevOps | Unhandled exception, broken config |
| 501 | Not Implemented | Developer | Endpoint exists but handler is missing |
| 502 | Bad Gateway | DevOps / SysAdmin | Upstream service crashed or unreachable |
| 503 | Service Unavailable | DevOps / SysAdmin | Server overloaded, maintenance mode |
| 504 | Gateway Timeout | DevOps / SysAdmin | Upstream service too slow to respond |
| 505 | HTTP Version Not Supported | Developer / DevOps | Client using unsupported HTTP version |
5xx errors mean something broke on the server side. These are usually the most urgent because they affect all users, not just one bad request.
500 Internal Server Error
The generic “something broke” code. The server ran into a condition it didn’t know how to handle. Your first move should always be checking the error logs:
# Nginx error log
sudo tail -50 /var/log/nginx/error.log
# Apache error log
sudo tail -50 /var/log/apache2/error.log
# Application logs (systemd service)
sudo journalctl -u your-app-service --since "10 minutes ago"
tail -50 shows the last 50 lines. journalctl -u filters logs by a specific systemd service, and --since limits the output to recent entries so you’re not scrolling through hours of logs.
502 Bad Gateway
This is one of the most common 5xx errors you’ll see in production. It means your reverse proxy (Nginx, HAProxy, etc.) is running fine, but the application behind it is down or unreachable. If you’re using a setup where HAProxy forwards to Apache or Nginx proxies to a Node.js app, a 502 means the upstream crashed.
Here’s how to diagnose it step by step:
# Check if the upstream service is running
sudo systemctl status your-app-service
# Check if the upstream port is actually listening
sudo ss -tlnp | grep :8080
# Test the upstream directly, bypassing the proxy
curl -I http://localhost:8080/health
ss -tlnp shows all listening TCP ports — -t for TCP, -l for listening, -n for numeric output, -p for process names. If the port isn’t listening, your app didn’t start or it crashed.
503 Service Unavailable
The server is up but can’t handle requests right now — either it’s overloaded or someone put it in maintenance mode. Check resource usage:
# CPU and memory overview
top -bn1 | head -20
# Disk space
df -h
# Active connections count
sudo ss -s
If the server is running out of memory or CPU, you might need to scale up or optimize your application. On EC2, you might also want to add swap space as a temporary buffer while you figure out the root cause.
504 Gateway Timeout
The upstream service is running but responding too slowly. Your reverse proxy gave up waiting. Common culprits: slow database queries, external API calls that hang, or large file processing without async handling.
Check the proxy timeout settings in your Nginx config:
location / {
proxy_pass http://localhost:8080;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
The defaults are 60 seconds each. You can increase these if your app legitimately needs more time — but the real fix is figuring out why the app is slow. Bumping timeouts is a band-aid, not a solution. If you’re dealing with Lambda functions timing out on long-running tasks, the same principle applies — check out How to Avoid AWS Lambda Timeout When Processing HubSpot Records for a pattern that works.
Quick Reference: Who Fixes What
| Code Range | Type | Who Fixes It |
|---|---|---|
| 1xx | Informational | No action needed |
| 2xx | Success | No action needed |
| 3xx | Redirection | Developer / SysAdmin |
| 4xx | Client Error | Developer / Security |
| 5xx | Server Error | DevOps / SysAdmin / Backend |
The key takeaway: 4xx codes are usually the developer’s problem (bad request, wrong endpoint, auth issues), while 5xx codes fall on DevOps and backend engineers (server crashes, resource exhaustion, misconfigured proxies). Some codes like 403 blur the line — it could be the client sending the wrong credentials, or the server blocking a valid request due to overly strict WAF rules.
Monitoring HTTP Errors in Production
Running curl manually works fine for debugging, but it shouldn’t be your main way to catch errors in production. You need monitoring that alerts you before users start complaining.
For a quick spot check, you can count status codes from your Nginx access logs:
sudo tail -1000 /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c | sort -rn
awk '{print $9}' extracts the status code field from Nginx’s default log format. sort | uniq -c | sort -rn counts each code and sorts by frequency, highest first. If you’re seeing a spike in 5xx errors, you know something is wrong.
For proper production monitoring, set up something like Datadog or Prometheus to track error rates and alert on anomalies. If you’re already collecting logs, you can parse them with Datadog Grok rules to extract status codes and build dashboards around them.
Conclusion
HTTP status codes are the first clue when something goes wrong with a request. Once you know what each code means and who should own the fix, troubleshooting gets a lot faster. Developers handle the 4xx logic bugs, DevOps takes care of 5xx infrastructure issues, and security reviews the 401/403 access problems. Less finger-pointing, faster resolution.
If you’re setting up a web server from scratch, check out How to Secure Nginx with Let’s Encrypt on Ubuntu 22.04 to make sure your HTTPS setup doesn’t accidentally cause redirect loops.