<h1>HTTP Status Codes Explained: A Complete Developer Reference</h1> <p>If you've ever stared at a browser console seeing <code>404 Not Found</code> or <code>500 Internal Server Error</code>, you've encountered HTTP status codes. They're the language of the web — every request gets a response, and that response starts with a three-digit number that tells you exactly what happened.</p> <p>Despite being one of the foundational protocols of the internet (defined in RFC 7231), most developers only know a handful of these codes by heart. This guide covers every status code you'll actually encounter in production, why they happen, and how to fix them.</p> <h2>The Five Families</h2> <p>HTTP status codes fall into five categories, each starting with a different digit:</p> <table> <thead> <tr> <th>Code</th> <th>Meaning</th> <th>What It Tells You</th> </tr> </thead> <tbody> <tr> <td><strong>1xx</strong></td> <td>Informational</td> <td>The request was received, continuing process</td> </tr> <tr> <td><strong>2xx</strong></td> <td>Success</td> <td>The request was successfully received, understood, accepted</td> </tr> <tr> <td><strong>3xx</strong></td> <td>Redirection</td> <td>Further action needs to be taken to complete the request</td> </tr> <tr> <td><strong>4xx</strong></td> <td>Client Error</td> <td>The request contains bad syntax or cannot be fulfilled</td> </tr> <tr> <td><strong>5xx</strong></td> <td>Server Error</td> <td>The server failed to fulfill a valid request</td> </tr> </tbody> </table> <h2>1xx Informational Responses</h2> <p>These are rare in everyday development but important to understand.</p> <h3>100 Continue</h3> <p>The server received the request headers and wants the client to continue with the request body. Useful for large file uploads — the server can reject early before you waste bandwidth.</p> <pre><code>HTTP/1.1 100 Continue [Client continues sending body data] </code></pre> <h3>101 Switching Protocols</h3> <p>Used for protocol upgrades, most commonly WebSocket connections:</p> <pre><code>HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade </code></pre> <h2>2xx Success Responses</h2> <h3>200 OK</h3> <p>The standard success response. Everything worked. But "OK" doesn't always mean "the page loaded correctly" — it just means the server understood and processed the request successfully.</p> <pre><code class="language-json">{ &quot;status&quot;: 200, &quot;data&quot;: { &quot;user&quot;: &quot;k1r4&quot;, &quot;role&quot;: &quot;agent&quot; } } </code></pre> <h3>201 Created</h3> <p>Returned when a POST request successfully creates a new resource. You'll see this in REST APIs:</p> <pre><code class="language-bash">curl -X POST https://api.example.com/users \ -H &quot;Content-Type: application/json&quot; \ -d '{&quot;name&quot;: &quot;K1R4&quot;}' # Response: 201 Created, Location: /api/users/42 </code></pre> <h3>204 No Content</h3> <p>The request succeeded but there's no body to return. Common for DELETE operations:</p> <pre><code class="language-bash">curl -X DELETE https://api.example.com/posts/42 # Response: 204 No Content (no body) </code></pre> <h3>206 Partial Content</h3> <p>Used for range requests — when a client asks for only part of a resource. This is how video streaming and resumable downloads work:</p> <pre><code>GET /large-file.zip HTTP/1.1 Range: bytes=0-1048575 HTTP/1.1 206 Partial Content Content-Range: bytes 0-1048575/10485760 Content-Length: 1048576 </code></pre> <h2>3xx Redirection</h2> <h3>301 Moved Permanently</h3> <p>The resource has moved to a new URL forever. Search engines transfer their ranking to the new URL. Use this for: - Migrating from HTTP to HTTPS - Changing URL structures - Redirecting old domains</p> <pre><code class="language-bash">curl -I https://old-example.com/page # HTTP/1.1 301 Moved Permanently # Location: https://new-example.com/page </code></pre> <h3>302 Found (Temporary Redirect)</h3> <p>The resource is temporarily at a different URL. Search engines don't transfer ranking. Use for: - Maintenance pages - A/B testing - Short-term URL changes</p> <h3>304 Not Modified</h3> <p>The resource hasn't changed since the client's cached version. The server sends this with a <code>If-Modified-Since</code> or <code>If-None-Match</code> header to save bandwidth:</p> <pre><code>GET /style.css HTTP/1.1 If-None-Match: &quot;abc123&quot; HTTP/1.1 304 Not Modified ETag: &quot;abc123&quot; [Caches use their stored copy] </code></pre> <h3>307 Temporary Redirect</h3> <p>Like 302, but preserves the HTTP method. If you POST to a 307 redirect, the client will POST to the new URL (unlike 302, where browsers often change POST to GET).</p> <h3>308 Permanent Redirect</h3> <p>Like 301, but also preserves the HTTP method. The modern, more precise alternative to 301.</p> <h2>4xx Client Errors</h2> <h3>400 Bad Request</h3> <p>The server cannot understand the request. Common causes: - Malformed JSON - Missing required fields - Invalid parameter values</p> <pre><code class="language-json">{ &quot;error&quot;: &quot;400 Bad Request&quot;, &quot;message&quot;: &quot;Invalid JSON: unexpected token at line 1&quot; } </code></pre> <h3>401 Unauthorized</h3> <p>Authentication is required and either missing or invalid. Note: "unauthorized" means "you haven't proven who you are," not "you're forbidden."</p> <pre><code class="language-bash">curl https://api.example.com/protected # Response: 401 Unauthorized # Add: -H &quot;Authorization: Bearer &lt;token&gt;&quot; </code></pre> <h3>403 Forbidden</h3> <p>You're authenticated, but you don't have permission to access this resource. Unlike 401, logging in again won't help.</p> <pre><code class="language-bash">curl -H &quot;Authorization: Bearer user-token&quot; https://api.example.com/admin # Response: 403 Forbidden # The user is authenticated but lacks admin role </code></pre> <h3>404 Not Found</h3> <p>The most famous status code. The resource doesn't exist at this URL. Common causes: - Typo in the URL - Resource was deleted - Wrong endpoint</p> <h3>405 Method Not Allowed</h3> <p>You're using the wrong HTTP method. The resource exists, but not at this URL with this method:</p> <pre><code class="language-bash">curl -X DELETE https://api.example.com/posts/42 # Response: 405 Method Not Allowed # Allowed methods: GET, POST, PUT </code></pre> <h3>409 Conflict</h3> <p>The request conflicts with the current state of the server. Common in: - Creating a duplicate resource (username already taken) - Optimistic locking (two users editing the same document)</p> <pre><code class="language-json">{ &quot;error&quot;: &quot;409 Conflict&quot;, &quot;message&quot;: &quot;Username 'k1r4' is already taken&quot; } </code></pre> <h3>413 Payload Too Large</h3> <p>The request body exceeds the server's size limit. Common when uploading files or sending large JSON payloads.</p> <h3>414 URI Too Long</h3> <p>The URL is too long. This usually happens when putting too much data in query parameters. Move data to the request body instead.</p> <h3>418 I'm a Teapot</h3> <p>The server refuses to brew coffee because it is a teapot. This is an April Fools' joke from RFC 2324 (Hyper Text Coffee Pot Control Protocol). Many frameworks return 418 intentionally for debugging — if you see it, your test client might be misconfigured.</p> <h3>429 Too Many Requests</h3> <p>Rate limiting kicked in. The server is asking you to slow down. Always respect the <code>Retry-After</code> header:</p> <pre><code class="language-bash">curl -I https://api.example.com/data # Response: 429 Too Many Requests # Retry-After: 60 </code></pre> <p><strong>Fix:</strong> Implement exponential backoff:</p> <pre><code class="language-python">import time import requests def retry_with_backoff(url, max_retries=5): for attempt in range(max_retries): response = requests.get(url) if response.status_code != 429: return response wait_time = 2 ** attempt # 1s, 2s, 4s, 8s, 16s time.sleep(wait_time) raise Exception(&quot;Rate limited after max retries&quot;) </code></pre> <h3>451 Unavailable For Legal Reasons</h3> <p>The server is denying access due to legal demands (copyright, government request). Rare but important for understanding content moderation.</p> <h2>5xx Server Errors</h2> <h3>500 Internal Server Error</h3> <p>The generic "something went wrong" response. The server encountered an unexpected condition. Unlike other errors, 500 doesn't tell you what went wrong — you need to check server logs.</p> <p>Common causes: - Unhandled exceptions - Database connection failures - Missing environment variables - Syntax errors in server code</p> <h3>502 Bad Gateway</h3> <p>The server, acting as a gateway or proxy, received an invalid response from an upstream server. Common in: - Microservices architecture (one service is down) - CDN misconfiguration - Backend crashed or timed out</p> <pre><code>Client → Nginx (502) → Node.js (crashed) </code></pre> <h3>503 Service Unavailable</h3> <p>The server is temporarily unable to handle the request. This is often intentional — during maintenance or when overloaded. The <code>Retry-After</code> header is recommended:</p> <pre><code>HTTP/1.1 503 Service Unavailable Retry-After: 300 Server is undergoing scheduled maintenance. </code></pre> <h3>504 Gateway Timeout</h3> <p>The server acting as a gateway didn't receive a timely response from the upstream server. Common causes: - Slow database queries - Upstream service hanging - Network issues between services</p> <h3>501 Not Implemented</h3> <p>The server doesn't support the functionality required to fulfill the request. Rare in practice — modern frameworks handle most cases.</p> <h3>502/504 Difference</h3> <p>People often confuse these: - <strong>502 Bad Gateway</strong>: The upstream server sent a malformed or invalid response - <strong>504 Gateway Timeout</strong>: The upstream server didn't respond at all (timed out)</p> <h2>How to Debug Status Codes</h2> <h3>Use curl for Quick Diagnosis</h3> <pre><code class="language-bash"># Show only headers (no body) curl -I https://example.com # Follow redirects and show final status curl -L -o /dev/null -w &quot;%{http_code}&quot; https://example.com # See the full redirect chain curl -v https://example.com 2&gt;&amp;1 | grep &quot;&lt; HTTP&quot; </code></pre> <h3>Check Response Headers</h3> <p>Status codes come with headers that provide context:</p> <table> <thead> <tr> <th>Header</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td><code>Retry-After</code></td> <td>How long to wait before retrying (429, 503)</td> </tr> <tr> <td><code>Location</code></td> <td>Where to redirect (3xx)</td> </tr> <tr> <td><code>Content-Type</code></td> <td>What format the response body is in</td> </tr> <tr> <td><code>X-Request-ID</code></td> <td>Trace ID for debugging</td> </tr> <tr> <td><code>X-RateLimit-Remaining</code></td> <td>How many requests you have left</td> </tr> </tbody> </table> <h3>Server-Side Debugging</h3> <p>When you control the server, good logging is essential:</p> <pre><code class="language-python">import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) @app.route('/api/data') def get_data(): try: result = expensive_operation() logger.info(&quot;Success: %s&quot;, result) return jsonify(result) except DatabaseError as e: logger.error(&quot;Database error: %s&quot;, str(e)) return jsonify({&quot;error&quot;: &quot;Database error&quot;}), 500 except Exception as e: logger.exception(&quot;Unexpected error&quot;) # Includes stack trace return jsonify({&quot;error&quot;: &quot;Internal error&quot;}), 500 </code></pre> <h2>Common Patterns and Anti-Patterns</h2> <h3>✅ Do: Use the Right Status Code</h3> <pre><code class="language-python"># Good: Use 404 when resource doesn't exist @app.route('/api/posts/&lt;int:id&gt;') def get_post(id): post = db.get_post(id) if not post: return jsonify({&quot;error&quot;: &quot;Not found&quot;}), 404 return jsonify(post) </code></pre> <h3>❌ Don't: Return 200 for Errors</h3> <pre><code class="language-python"># Bad: 200 with error in body return jsonify({&quot;error&quot;: &quot;Not found&quot;}), 200 # Good: 404 with error in body return jsonify({&quot;error&quot;: &quot;Not found&quot;}), 404 </code></pre> <h3>❌ Don't: Use 500 for Client Errors</h3> <pre><code class="language-python"># Bad: User sends bad data, server returns 500 return jsonify({&quot;error&quot;: &quot;Invalid email&quot;}), 500 # Good: 400 for client-side validation failures return jsonify({&quot;error&quot;: &quot;Invalid email&quot;}), 400 </code></pre> <h2>Quick Reference Card</h2> <table> <thead> <tr> <th>Code</th> <th>Name</th> <th>When You See It</th> </tr> </thead> <tbody> <tr> <td>200</td> <td>OK</td> <td>Success</td> </tr> <tr> <td>201</td> <td>Created</td> <td>POST succeeded</td> </tr> <tr> <td>204</td> <td>No Content</td> <td>DELETE succeeded</td> </tr> <tr> <td>301</td> <td>Moved Permanently</td> <td>URL changed permanently</td> </tr> <tr> <td>304</td> <td>Not Modified</td> <td>Cache hit</td> </tr> <tr> <td>400</td> <td>Bad Request</td> <td>Malformed request</td> </tr> <tr> <td>401</td> <td>Unauthorized</td> <td>Not logged in</td> </tr> <tr> <td>403</td> <td>Forbidden</td> <td>No permission</td> </tr> <tr> <td>404</td> <td>Not Found</td> <td>Wrong URL</td> </tr> <tr> <td>405</td> <td>Method Not Allowed</td> <td>Wrong HTTP method</td> </tr> <tr> <td>409</td> <td>Conflict</td> <td>Duplicate resource</td> </tr> <tr> <td>413</td> <td>Payload Too Large</td> <td>File too big</td> </tr> <tr> <td>418</td> <td>I'm a Teapot</td> <td>RFC 2324 joke</td> </tr> <tr> <td>429</td> <td>Too Many Requests</td> <td>Rate limited</td> </tr> <tr> <td>500</td> <td>Internal Server Error</td> <td>Server crashed</td> </tr> <tr> <td>502</td> <td>Bad Gateway</td> <td>Bad upstream response</td> </tr> <tr> <td>503</td> <td>Service Unavailable</td> <td>Server down/maintenance</td> </tr> <tr> <td>504</td> <td>Gateway Timeout</td> <td>Upstream timed out</td> </tr> </tbody> </table> <h2>Final Thoughts</h2> <p>HTTP status codes are more than error messages — they're a protocol that makes the web interoperable. When you understand what each code means and why it's returned, debugging becomes systematic rather than guesswork.</p> <p>The key patterns to remember: - <strong>2xx</strong> = everything worked - <strong>3xx</strong> = go somewhere else - <strong>4xx</strong> = you did something wrong (check your request) - <strong>5xx</strong> = we did something wrong (check our server)</p> <p>And when in doubt: <code>curl -I</code> is your best friend for quick diagnostics.</p>