当客户端请求一个资源时,如果Nginx认识请求方法,但目标文件或location规则不允许使用该方法,就会返回405 Not Allowed。这个状态码和后端框架里的405很像,但触发位置完全不同。很多线上问题正是因为把Nginx的405误判为后端返回,导致反复修改接口代码却没有效果。

要快速定位这类问题,需要先理解Nginx在处理请求时的方法限制来自哪里。Nginx本身并不对每个location默认限制HTTP方法,真正常见的405源头有两个:静态文件处理模块和接入层规则。
一、Nginx 405的触发原理与典型场景
HTTP 405 Not Allowed表示服务器已经识别到了请求方法,但当前目标资源不支持该方法。举个例子,如果客户端向一个只允许GET的接口发送POST,符合HTTP语义的响应就是405。但Nginx产生405的场景往往更隐蔽,它不一定涉及后端应用,有时仅仅是静态文件模块在起作用。
Nginx默认启用静态文件处理模块,当一个请求匹配到带root或alias的location,并且没有proxy_pass、fastcgi_pass等代理指令时,Nginx会尝试直接读取磁盘文件返回。该静态模块对HTTP方法的支持并不是无限的,它只接受GET、HEAD和POST,如果收到PUT、DELETE或PATCH请求,Nginx会直接返回405,而不会把请求转发给任何后端。例如客户端执行PUT /uploads/avatar.jpg时,如果该路径是纯静态目录,返回405就属于Nginx自身行为。
另一个容易混淆的配置是limit_except指令。它的作用是限制某个location内允许使用的HTTP方法,但行为与静态模块不同,未被允许的方法通常返回403。可是如果limit_except嵌套的配置里又走了静态文件逻辑,或者使用了error_page把403转成其他状态码,最终也可能看到405。因此排查时不能只看配置名称,必须结合访问日志中的URI和location匹配结果判断。
# 纯静态目录,PUT/DELETE会返回405
location /uploads/ {
root /var/www/data;
autoindex off;
}
二、排查思路与修复配置
遇到Nginx返回405,第一步是确认请求方法和完整URI。Nginx的access.log默认会记录请求方法、URI、状态码和来源IP,如果日志里能看到这条请求的状态码是405,并且请求方法不是GET或HEAD,就基本可以判断是在Nginx层被拦截。接着使用nginx -T命令查看最终合并后的配置,重点确认该URI实际匹配到了哪一个location,以及这个location内是否有proxy_pass、fastcgi_pass、uwsgi_pass等转发指令。
如果请求本来应该转发给后端,但误匹配到了静态目录,就会产生405。常见原因是location前缀写得太宽,例如有一个location /用来处理静态资源,而后端接口也位于根路径下,却没有单独配置更精确的location。正确的是为接口路径单独设置代理规则,并保证其优先级高于静态规则。下面是一个常见修复方式:
# 接口路径优先转发到后端
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 静态资源单独处理
location / {
root /var/www/html;
index index.html;
}
上面配置中,/api/开头的请求会进入代理location,不会落到静态根目录。即使客户端发送POST /api/upload,也能正常转发到后端8080端口。需要特别检查的是location的匹配优先级:Nginx会优先匹配最长前缀,精确匹配和正则匹配还有不同规则。如果原来的配置把静态root放在/下面,同时又没有为API声明单独的location,那么所有POST接口都可能被当成静态文件处理,轻则返回405,重则暴露目录结构。
如果业务上确实需要对静态路径使用PUT或DELETE方法,比如某些客户端会向静态存储路径发送PATCH做局部更新,单纯依靠Nginx静态模块无法实现。这时应该把这类请求反向代理到后端服务处理,或者改用支持这些方法的模块。用一个error_page把405转成200虽然能临时消除报错,但容易掩盖真正的方法错误,不建议长期使用。
三、跨域预检与避坑建议
跨域场景是Nginx 405的高发区。浏览器在发送跨域POST、PUT或带自定义头的请求前,通常会先发送一个OPTIONS预检请求。这个OPTIONS请求会携带Access-Control-Request-Method和Origin头,目标是确认服务器是否允许接下来的真实请求。如果OPTIONS请求落在了Nginx的静态location内,或者没有对应的代理规则,Nginx静态模块会因为OPTIONS不在支持的方法列表里而返回405。浏览器收到405后会直接阻断后续请求,前端控制台通常显示CORS错误,但服务端日志里却只有405。
解决跨域预检405的关键是提前处理OPTIONS。比较稳妥的做法是在代理location内,当请求方法为OPTIONS时直接返回204,并附加CORS响应头。配置可以这样写:
location /api/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods GET, POST, PUT, DELETE, OPTIONS;
add_header Access-Control-Allow-Headers Authorization, Content-Type, X-Requested-With;
add_header Access-Control-Max-Age 3600;
return 204;
}
proxy_pass http://127.0.0.1:8080;
}
这里把OPTIONS交给Nginx直接返回,避免预检请求继续打到后端,也能减少后端压力。需要注意,if指令在Nginx中通常要谨慎使用,但针对$request_method的判断属于非常轻量的逻辑,在代理location里是常见且可接受的写法。同时CORS头不要重复添加,否则可能出现两个Access-Control-Allow-Origin头导致浏览器校验失败。
避坑方面还有几个细节。第一是修改完配置后必须执行nginx -t,确保语法正确;使用nginx -s reload平滑加载,不要直接重启,尤其是长连接场景。第二是要区分root和alias的路径差异,如果静态文件实际路径不对,Nginx可能返回404而不是405,但部分配置会通过try_files把路径重新解析,最终依旧可能落到方法限制上。第三是不要把405单独理解为后端路由问题,排查时先看Nginx访问日志再决定是查配置还是查接口代码,可以节省大量排障时间。第四是如果必须使用error_page处理405,请确认跳转后的页面或接口能正确处理原始请求方法,否则会把一个明确的错误变成一个更隐蔽的异常。