The Error
You send a POST request โ from a form, fetch(), or curl โ and Nginx returns:
405 Method Not Allowed
The response headers include:
Allow: GET, HEAD
Your backend never receives the request. Nginx kills it first.
Root Cause
Nginx's static file module (ngx_http_static_module) only handles GET and HEAD. When a POST request hits a location block that resolves to a file on disk, Nginx rejects it immediately with 405. Your app never sees it. This happens with root, alias, and try_files alike.
Four patterns that trigger this:
- A form with
action="/submit"pointing to a URL matched by a staticrootblock - A React or Vue SPA where
try_files $uri /index.htmlcatches all routes โ including your backend API calls - Location block ordering: the static catch-all
location /matches before a more specific proxy block - An API endpoint like
/api/loginaccidentally served as a static file
Diagnose First
Before changing anything, find which location block is handling your request:
sudo nginx -T | grep -A 10 "location"
Then test with curl and check what comes back:
curl -sv -X POST https://yourdomain.com/api/submit \
-H "Content-Type: application/json" \
-d '{"key":"value"}' 2>&1 | grep -E "&1 | grep X-Debug-Upstream
- Header present with an IP: the 405 is coming from your app server. Fix it in the app, not Nginx.
- Header absent: Nginx generated the 405 itself. Apply Fix 1 or Fix 2.
Verify the Fix
After editing the config, check syntax and reload:
# Check syntax
sudo nginx -t
# Reload without downtime
sudo nginx -s reload
Retest the failing request:
curl -sv -X POST https://yourdomain.com/api/submit \
-H "Content-Type: application/json" \
-d '{"test": true}'
A successful fix gives you a real backend response โ 200, 201, or even a validation error from your app. Anything except 405. Still seeing 405? Make sure the config file you edited is actually loaded:
sudo nginx -T | grep "include"
Prevention
- Always define specific location blocks before generic ones. Put
/api/above/in every server block that mixes static files and a backend proxy. - Keep
try_filesand API proxying in separate blocks. One block for the SPA fallback, a separate one for the proxy โ no exceptions. - Test all HTTP methods in staging. A one-liner after every deploy catches this immediately:
curl -X POST https://staging.example.com/api/health. - Tail the error log during development. The 405 shows up in
/var/log/nginx/error.loglong before you notice it in a browser:tail -f /var/log/nginx/error.log.

