- A 405 means the URL exists, but the method is wrong. Think
POSTsent to a route that only acceptsGET, not a missing page. - Start with the
Allowheader. It tells you which methods the resource currently accepts and is the fastest clue you’ll get from the server. - Debug layer by layer. Check the client, then CDN or WAF, then Nginx or Apache, then the framework router.
- A lot of 405 incidents are really contract problems. The code, proxy rules, and docs drift apart, and production is where that mismatch shows up.
Table Of Contents
A feature works on localhost. Your form submits, your React app gets a clean response, your integration test passes. Then the same request hits staging through a CDN, a reverse proxy, and a production app server, and suddenly you get 405 Method Not Allowed.
That failure is maddening because it looks simple and often isn’t. The route exists. DNS is fine. The app is up. You’re not staring at a 500 or a dead service. You’re looking at a request that made it far enough for some layer to say, “I know this resource. I just won’t accept that verb here.”
Introduction The Frustrating 405 Error
I’ve seen http status 405 show up in exactly the kind of release that feels safe. A small frontend change. A route rename that looked harmless. A proxy rule added for security. Everything appears normal until someone tries to POST from the live environment.

HTTP 405, formally Method Not Allowed, is a long-established client error in the HTTP family. One useful detail makes it different from many other errors: the response should include an Allow header listing the permitted methods, which makes the error intentionally self-describing according to Abstract API’s HTTP 405 reference.
That matters in practice. When a response says Allow: GET, HEAD, OPTIONS, the server is giving you a direct hint instead of forcing you to guess whether the path is wrong, the app is down, or the resource doesn’t exist.
Practical rule: If you see a 405, stop treating it like a generic failure. It’s a method mismatch until proven otherwise.
Production stacks make this trickier than localhost. A browser sends one thing. A proxy rewrites it. A WAF blocks it. Nginx applies method rules. Then your framework router gets the request that survived all of that. If you start by blaming application code, you can burn a lot of time in the wrong layer.
What Exactly is a 405 Method Not Allowed Error
The cleanest way to think about a 405 is this: the key fits the door, but you’re turning it the wrong way. The URL is valid. The server knows the resource. It just refuses the HTTP method used on that resource.
A 404 says “I can’t find that path.” A 405 says “that path exists, but not for this verb.”

The header that matters most
When you’re debugging, the Allow header is the first thing worth checking. RFC-derived guidance says a server must send an Allow header in a 405 response listing the methods supported by that resource. That makes 405 useful not just for humans, but also for clients, gateways, and observability tooling, as noted in MDN’s source content for status 405.
A response might look like this:
HTTP/1.1 405 Method Not AllowedAllow: GET, HEAD, OPTIONSContent-Type: application/json
That single header tells you a lot:
GETis allowed for reading the resourceHEADis allowed for metadata checksOPTIONSis available for capability discoveryPOSTis not accepted here, at least right now
405 vs 501
This distinction matters in API design and debugging.
| Status | Meaning | Scope |
|---|---|---|
| 405 | Method recognized but refused for this resource | Resource-specific |
| 501 | Method not implemented by the server | Server-wide capability |
If your route is read-only and a client tries POST, PUT, or DELETE, 405 is the right answer. If the server doesn’t support that method at all, 501 is the better fit.
Don’t treat 405 as a weaker 500. It’s usually a precise policy signal.
That precision is why 405 is so helpful when the stack preserves it properly. It narrows the search area fast.
A Debugging Checklist for Finding the Root Cause
The mistake I see most often is jumping straight into controller code. With modern stacks, a 405 can come from several places before your app ever sees the request. The fastest path is a layered check.
Start outside the application
First, inspect the actual response, not what your frontend logger summarized.
Look for:
- The
Allowheader. That tells you what the responding layer thinks is valid. - Response headers that reveal ownership. CDN, gateway, reverse proxy, or framework-specific headers often tell you who generated the response.
- Differences between local and production paths. A frontend app may call
/api/itemslocally but hit/v1/itemsthrough a proxy in production.
MDN’s coverage points out an under-explained reality: 405 is often a routing-contract problem between frontend, API, and infrastructure layers, and the practical workflow is to inspect Allow, compare route definitions, then verify whether edge infrastructure is rewriting or blocking methods before the request reaches the app in MDN’s 405 guidance.
Move layer by layer
I use this sequence:
Client request
- Is the browser form defaulting to
GET? - Is
fetch()sendingPOSTwhere the route only supportsPUT? - Is an SDK wrapper changing the method under the hood?
- Is the browser form defaulting to
CDN, WAF, or reverse proxy
- Are certain verbs blocked at the edge?
- Is a preflight
OPTIONSrequest failing before the main request happens? - Is method override middleware expected locally but missing in production?
Web server
- Nginx
limit_except - Apache
<Limit>rules - Route-specific blocks that allow reads but deny writes
- Nginx
Application framework
- Route exists, but only for
GET - Resource is mounted under a namespace you didn’t deploy
- Middleware short-circuits before controller logic
- Route exists, but only for
A good way to test the contract quickly is to hit the endpoint directly with curl or Postman and compare methods one by one. If you’re also reviewing endpoint behavior more broadly, this guide on testing RESTful APIs is a useful companion because it forces you to verify the method, path, headers, and expected response together.
If localhost works and production fails, assume there’s an extra layer in production enforcing method policy.
Watch for false leads
Some teams label any rejected API call as “405” in logs or UI messages, even when the actual issue is elsewhere. Don’t trust the frontend label. Trust the actual wire response.
Fixing 405 Errors on Popular Servers and Frameworks
A lot of 405s come from explicit method controls in web servers. Apache and Nginx can allow or deny methods by configuration, which is why a valid URL can still fail if the verb isn’t permitted, as described in Sitechecker’s explanation of 405 behavior.

Nginx
This is a common source of production-only surprises.
location /api/ { limit_except GET POST { deny all; }}
That block allows GET and POST, but rejects PUT, PATCH, or DELETE.
If your app supports updates, adjust the allowed set:
location /api/ { limit_except GET POST PUT PATCH DELETE { deny all; }}
Check for a second trap too. A different location block may catch the route you think you’re hitting.
Apache
Apache can enforce method rules with <Limit> or related directives.
<Limit GET POST> Require all granted</Limit>
That looks harmless until your client tries PUT.
If the endpoint should allow more methods, update the rule to match the route contract:
<Limit GET POST PUT DELETE> Require all granted</Limit>
When teams run into request-size issues alongside method issues, they often debug the wrong thing first. If you’re sorting out adjacent upload failures, this write-up on status code 413 is worth keeping nearby because the symptoms can get mixed together in API clients.
Express.js
In Express, the route may exist but only for one verb.
app.get('/users', (req, res) => { res.json(users);});
If the frontend sends:
fetch('/users', { method: 'POST' })
you’ll get a method mismatch unless you define the handler:
app.post('/users', (req, res) => { const user = createUser(req.body); res.status(201).json(user);});
If you want clearer diagnostics, add an explicit method handler for unsupported verbs and return a 405 with Allow.
A short walkthrough can help if you want to see the mechanics visually:
Django
Django views often fail when the allowed methods aren’t declared or the wrong class-based view is used.
Function-based example:
from django.views.decorators.http import require_http_methodsrequire_http_methods(["GET", "POST"])def users(request): ...
If your endpoint needs DELETE, that decorator has to reflect reality.
For class-based views, check which methods are implemented:
from django.views import Viewclass UserView(View): def get(self, request): ... def post(self, request): ...
No put() method means no PUT support.
Ruby on Rails
Rails usually keeps method intent in routes.rb.
resources :users, only: [:index, :show]
That route set won’t accept create, update, or destroy.
To support creation:
resources :users, only: [:index, :show, :create]
Then make sure the controller action exists:
def create # create userend
Worth checking: The route can be correct in code but missing in the deployed environment because an old build or stale container is still serving traffic.
Client-Side Checks and Why Your API Docs Are Key
Some 405s are embarrassingly small. An HTML form defaults to GET because nobody set method="POST". A frontend helper switched from POST to GET during a refactor. A generated client kept using an old endpoint signature after the backend changed.

Client checks that catch real bugs
Before touching the server, verify these:
HTML forms
- Did the form declare
method=\"post\"? - Is the action URL the same path the backend exposes?
- Did the form declare
JavaScript requests
- Does
fetch()or Axios send the intended method? - Are redirects changing request behavior in a way your client library hides?
- Does
Browser behavior
- Is an
OPTIONSpreflight happening before the actual call? - Does the browser hit a different origin in production than in dev?
- Is an
If your team is building automation-heavy workflows, resources on connecting apps without custom code are useful because they force you to think in terms of API contracts and method compatibility, not just code snippets.
A lot of 405s are documentation failures
This is the part most engineering teams underweight. A backend route changes from POST /sessions/refresh to PUT /sessions/refresh, but the public docs still show POST. Every consumer follows the docs, gets a 405, and opens a bug against the server.
That’s not really a server failure. It’s contract drift.
A strong contrarian take from Hostwinds’ 405 discussion is that many 405 incidents are documentation failures, not only server failures. The endpoint may be correct, but the docs or examples omit method constraints, and teams end up patching the wrong layer.
Here’s what healthy API documentation should make unambiguous:
| Doc element | What it must show |
|---|---|
| Endpoint example | Exact method and path |
| Reference block | Allowed methods for the resource |
| Sample client code | Matching verb in every language example |
| Change history | When method support changed |
If you maintain API references in GitHub, it’s worth tightening your process around API documentation best practices so method changes don’t land in code without landing in docs.
A 405 often means the request is wrong. It can also mean the docs taught the client to be wrong.
Adopting a Proactive Stance on API Errors
The teams that handle http status 405 well don’t just fix the incident. They reduce the chance of seeing the same class of bug again.
That starts with better visibility. Log the request method, path, response status, and the component that generated the response. If your edge layer can return a 405, make that obvious in headers or logs so app engineers don’t waste time debugging routes that never received the request.
It also means reviewing method policy as part of change management. When someone edits routing, proxy rules, or server config, the API contract changed whether they meant to or not.
For teams improving their broader toolchain, directories of curated AI agents and developer workflow tools can be handy for evaluating what belongs in a modern documentation and delivery workflow, especially if you’re trying to tighten the connection between code, docs, and operational checks.
The practical standard is simple:
- Check
Allowfirst - Trace ownership layer by layer
- Keep route definitions, edge rules, and docs aligned
- Treat method support as part of the API contract, not an implementation detail
A 405 is rarely mysterious once you stop treating it as a generic web error. It’s usually a precise signal that one part of your stack believes the contract is different from another.
If your API docs keep drifting from your routes, DeepDocs is worth a look. It keeps documentation in sync with code changes inside GitHub, which helps teams avoid the kind of stale method examples and route mismatches that turn into avoidable 405s.

Leave a Reply