405
The path exists and the server recognises the verb — it just does not accept that verb here, and it is required to hand you the list of verbs it does accept.
The same three digits are three different problems depending on which side of the request you are on. Read the block that describes you.
Why you are seeing it
The most common 405 in a browser is one you never wrote. A cross-origin request carrying a JSON content type or a custom header is preflighted, so the browser sends an `OPTIONS` request first, and a server whose router only registers GET and POST answers that preflight with 405. What you then see in the console is a CORS error, because the preflight did not succeed — and the CORS message hides the 405 underneath it, so people spend hours on `Access-Control-Allow-Origin` for a problem that is a missing route. The second shape is a POST that arrives as a GET. RFC 9110 §15.4.2 records that user agents have historically rewritten POST to GET when following a 301 or 302, so a form posted to an `http://` URL that redirects to `https://`, or to a path that redirects to add a trailing slash, reaches the server as the wrong verb entirely.
What to do
Read the method of the failed request in the network panel before anything else, because it is frequently not the method you called. If it says OPTIONS, the work is server-side — answer preflights with the CORS headers and a 204 — and no change to your `fetch` will help. Then read the `allow` header on the 405 itself: §15.5.6 makes it mandatory, and it is a direct statement of which verbs that path takes, which usually ends the investigation on the spot. If a redirect is in the chain, send the request to the final URL instead, and prefer servers that use 307 or 308 for anything that might be a POST, since those are the two redirects that preserve the method. Finally, purge the edge after you fix it, because a cached 405 is a real possibility here.
Why you are seeing it
The router matched the path and not the verb — or something rewrote one of them on the way in. Frameworks disagree about how to say this: some answer 405 with a correct `Allow` header, some answer 404 for an unmatched verb, so the identical bug looks like two different problems in two of your own services. Static file serving is its own case: nginx's static module serves only GET and HEAD, so a POST aimed at a path that resolves to a file on disk comes back as `405 Not Allowed` from nginx rather than from your application, which is why the request never appears in your application log. Path rewriting supplies the rest: a rewrite that adds or strips a trailing slash can move a POST onto a route registered only for GET, and a proxy that redirects `/api/users` to `/api/users/` gives the client the chance to downgrade the method on the way.
What to do
Dump the route table and compare method and path as a pair, not as two separate facts — every framework has a command for this and it is faster than reading the router. Confirm your 405s actually carry `Allow`, since it is a MUST and it is the single most useful thing the response can say; a framework that omits it is turning a one-minute client-side fix into a support conversation. Check specifically whether you are 405ing `HEAD` or `OPTIONS` by accident: §9.1 requires GET and HEAD on general-purpose servers, and OPTIONS is what every browser preflight uses, so those two are the ones most likely to break something you did not test. If the 405 never reaches your application log, look at the static file handler and the rewrite rules instead. And purge the CDN afterwards, because §15.1 makes this response cacheable by heuristic.
Why you are seeing it
You are very unlikely to have caused this one. It usually appears after you submitted a form — instead of a confirmation you get a short error page — and it means the site's form is pointed at an address that does not accept submissions. It can also show up after following an old bookmark or a link that switched between the `www.` and plain versions of a site, because the redirect on the way can turn your submission into an ordinary page request. Nothing is wrong with your browser, your device or the data you entered.
What to do
Do not reload the error page — a reload sends the same rejected submission again and it will fail identically, and some browsers will also warn you about resubmitting. Go back to the form, start from the site's home page over `https://` so nothing is redirected on the way, and submit once. If the site has both a `www.` and a non-`www.` address, use whichever one the site's own links use. If it still fails, this is a wiring mistake on the site's side, and the useful thing to report is the exact page you were on and which button you pressed.
Run this against the URL that failed. It prints the status without the error page, so you can see what the server said rather than what the browser rendered.
curl -sS -X POST -o /dev/null -D - -w '\n%{http_code}\n' https://example.com/`-X POST` replaces the method curl would otherwise use, `-o /dev/null` discards the error page, and `-D -` dumps the response headers, which is where the answer is. The header to find is `allow`: §15.5.6 makes it mandatory on a 405 and it lists the methods that path really accepts, so a single run tells you whether your call was wrong or your route is. Read its absence as a finding too — a 405 with no `Allow` is a server that is not answering to spec, and an `Allow` present but empty legitimately means the resource accepts nothing at all. Two variants are worth the extra seconds. `curl -I` sends a HEAD rather than a GET, so a 405 you get from `-I` may be about HEAD alone and not about the verb you actually care about, which §9.1 makes a server defect where GET works. And to reproduce a browser preflight rather than guess at it, run `curl -sS -D - -o /dev/null -X OPTIONS -H 'origin: https://app.example.com' -H 'access-control-request-method: POST' https://example.com/api/` — a 405 there is the real cause behind a CORS message that never mentions it.
RFC 9110 §15.5.6 defines 405 as the method in the request line being known by the origin server but not supported by the target resource, and it makes the useful half mandatory: the origin server MUST generate an `Allow` header field in a 405 response listing the target resource's currently supported methods. `Allow` is defined in §10.2.1, and it has one detail worth knowing before you misread it — an empty `Allow` field value is legal and means the resource supports no methods at all, which is not the same as the header being absent. Two neighbouring codes bound the meaning. §15.6.2 reserves 501 (Not Implemented) for a method the server does not support for any resource, so 405 is the narrower statement: the server knows this verb, just not here. And §9.1 requires that all general-purpose servers support GET and HEAD, which makes a 405 in response to a HEAD on a resource whose GET works a defect rather than a policy — worth remembering because `curl -I` sends HEAD, so the tool you reach for first can manufacture the very code you are investigating. The last property is the one that outlives the deploy: 405 is one of the status codes RFC 9110 §15.1 lists as heuristically cacheable, alongside 404, 410 and 501, which means a cache or CDN may store one with no explicit `Cache-Control` at all. A route that answered 405 for ten minutes during a bad rollout can therefore keep answering 405 from the edge after the rollout is fixed, until the stored response expires or somebody purges it — the same trap 404 has, and for the same reason.
| Confused with | How to tell them apart |
|---|---|
501 | 501 (Not Implemented, §15.6.2) means the server does not support the method for any resource; 405 means it supports the method but not on this one. The practical difference is where you go next: a 501 for `PATCH` from a proxy or an old server is a capability gap you route around, while a 405 for `PATCH` from your own application is a route you did not register. Both are heuristically cacheable under §15.1, so both can outlive their cause. |
| 404 | A router that answers 404 for an unmatched verb hides the more useful half of the answer, and §15.5.6 is the reason the distinction matters: 405 comes with `Allow`, and 404 comes with nothing. The test takes one command — if a path returns 404 for POST and 200 for GET, it is a 405 wearing the wrong number, and you should be looking at the method rather than at the path. |
| 301 | The 405 you are looking at may be an artefact of a redirect rather than of your call. §15.4.2 records that user agents have historically rewritten POST to GET when following 301, and the same is true in practice of 302, so a POST that crosses an `http://` to `https://` or a trailing-slash redirect can arrive as a GET on a route that only accepts POST. 307 and 308 are the redirects that preserve the method, which is why an API should use those. |
CORS preflight | A cross-origin request with a JSON body or a custom header is preceded by an `OPTIONS` request the browser generates itself, and a server without an OPTIONS route answers it with 405. The browser then reports a CORS failure, because the preflight did not succeed — so the message names the wrong problem, and the 405 sitting in the network panel one row above the failed request is the real one. |
Allow | §10.2.1 defines `Allow` as the list of methods supported by the target resource, and §15.5.6 makes sending it mandatory on a 405 — so its absence is a server bug rather than a hint. Read an empty value carefully: an empty `Allow` field value is a legal way to say the resource supports no methods at all, which is a different statement from the header not being there. |
All of these run in your browser — nothing is uploaded.
Codes people usually end up reading in the same session as 405.
Read the response headers rather than the page. curl -sS -o /dev/null -D - https://example.com/path prints them without the body, and server, via and cf-ray between them name the layer that answered. A cf-ray value means Cloudflare handled the response and is the id their support will ask for. To take the edge out of the picture entirely, repeat the request with --resolve example.com:443:203.0.113.10, which connects to the origin address you name while still sending the original host name and SNI — if the answer changes, the edge and the origin disagree.
No, and you should never branch on it. RFC 9112 tells clients to ignore the reason phrase, servers are free to change it, and HTTP/2 and HTTP/3 do not carry one at all — so 404 Not Found over HTTP/1.1 arrives as a bare 404 over HTTP/2. The phrases on this site are the registered ones because they are what people search for and what appears in an HTTP/1.1 log, not because any software depends on them.
Retry 5xx and 429; do not retry 4xx, because nothing about the request will be different next time. If the response carries Retry-After, honour it — RFC 9110 defines it for exactly this, and it may be either a number of seconds or a date. Otherwise use exponential backoff with jitter and a hard cap, and only for idempotent methods: a retried POST can charge a card twice. A retry storm against a server that is already failing is how a brief incident becomes a long one.
Yes, and the only rule that matters is that the code has to be true. Anything that reads your responses automatically — search engines, monitoring, caches, client retry logic — makes decisions from the number and never from the page. Serving an error page with 200 hides the failure from your own alerting; serving 200 for a missing page gets the error indexed as content; returning 500 for a request that was simply malformed sends whoever is on call to the wrong half of the stack.
Still stuck on 405? browse every status code in the reference — or go back up to the block written for your side of the stack.