HTTP 405 Method Not Allowed: What It Means and How to Fix It
HTTP 405 Method Not Allowed is a status code meaning the server knows the request method, such as POST or DELETE, but the target resource does not support it. RFC 9110 requires the server to send an Allow header listing the methods the resource does accept, which is the fastest way to see what went wrong.
- Code
- 405
- Name
- Method Not Allowed
- Class
- 4xx client error
- Retry?
- No, fix the cause first
What causes a 405 error?
- →Sending POST to a URL that only serves GET, or the reverse.
- →A form or API route that moved, so the old URL now hits a static page.
- →A redirect that turned a POST into a GET, or a trailing-slash redirect on an API route.
- →A WAF or static host that only allows GET and HEAD.
How do you fix a 405 error when web scraping?
- →Read the
Allowheader and use one of the listed methods. - →Reproduce the request in a browser with DevTools open, and copy the method, URL, and body the page actually sends.
- →Check for redirects. Many clients switch POST to GET after a 301 or 302, which lands on a GET-only URL.
- →Do not retry with the same method. It will keep failing.
How do you fix a 405 error on your own server?
- →Send the
Allowheader with every 405, as RFC 9110 requires. - →Return 404 rather than 405 for routes that do not exist at all.
- →Use 307 or 308 redirects for API routes so clients keep the original method and body.
How do you handle a 405 error in a retry loop?
405 is not in RETRYABLE, so raise_for_status() raises on the first response instead of spending retries on a request that will fail the same way. Fix the cause, then send the request again.
import random
import time
import requests
RETRYABLE = {408, 429, 500, 502, 503, 504, 520, 521, 522, 523, 524}
def fetch(url: str, max_attempts: int = 5) -> requests.Response:
for attempt in range(max_attempts):
try:
response = requests.get(url, timeout=(10, 60))
except requests.Timeout:
time.sleep(2**attempt + random.uniform(0, 1))
continue
if response.status_code not in RETRYABLE:
response.raise_for_status()
return response
retry_after = response.headers.get("Retry-After", "")
backoff = 2**attempt + random.uniform(0, 1)
time.sleep(min(int(retry_after) if retry_after.isdigit() else backoff, 60))
raise RuntimeError(f"Gave up on {url} after {max_attempts} attempts")
How does Context.dev handle a 405 error?
Scrape loads each URL in a browser the way a visitor would, so it does not send the POST, PUT, or DELETE requests that usually trigger a 405. When a page only shows data after an interaction, browser actions on a paid plan can click, scroll, and wait before the page is captured.
See what the web scraping API does on every request, or read how to fix HTTP errors in web scraping for a longer walkthrough.
Frequently asked questions about a 405 error
How do I fix a 405 Method Not Allowed error?
Read the Allow header in the response, switch to one of the listed methods, and check whether a redirect changed your method on the way.
What is the difference between 405 and 404?
A 404 means nothing is at the URL. A 405 means the URL exists but does not accept the method you used.
Can a 405 be caused by a firewall?
Yes. Some WAFs and static hosts reject every method except GET and HEAD with a 405, even on URLs your application would accept.
Which status codes are related to 405?
Sources
Last reviewed