这里主要是为了记录在使用 Nginx 的时候遇到的问题及其处理解决方法。
诡异的问题时常发生,如果正确的处理它们呢?将是一个值得思考和学习的问题!通过对错误和异常问题的排除和处理,能够增长我们的知识储备已经掌握处理问题最为有效和使用的方式。这里将会记录常见的集群使用的问题,以备不时之需。

# 获取帮助信息
$ nginx -h
nginx version: nginx/1.18.0 (Ubuntu)
Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]
Options:
-?,-h : this help
-v : show version and exit
-V : show version and configure options then exit
-t : test configuration and exit
-T : test configuration, dump it and exit
-q : suppress non-error messages during configuration testing
-s signal : send signal to a master process: stop, quit, reopen, reload
-p prefix : set prefix path (default: /usr/share/nginx/)
-c filename : set configuration file (default: /etc/nginx/nginx.conf)
-g directives : set global directives out of configuration file
# 值得一说的就是 -s(发送信号)/-g(从配置文件中设置全局指令) 参数
stop: 快速停止nginx服务
quit: 完整有序的停止nginx服务
reopen: 重新打开日志文件(日志文件mv等操作后会重新生成)
reload: 修改配置后重新加载生效
# 简述reload命令的执行步骤
1. master进程检查配置文件的正确性,若错误则返回错误信息并终止(采用原配置文件继续工作;因为worker并未受到影响),若正常则继续后续步骤。
2. 用新的配置文件,启动新的worker进程。
3. nginx将新的请求,分配新的worker进程。
4. 等待以前的worker进程的全部请求已经都返回后,关闭相关worker进程。
5. 重复上面过程,知道全部旧的worker进程都被关闭掉。
1. 提示 Cookie 长度超限
报错信息:Request Request Header Or Cookie Too Large
- [问题起因] 服务新建之后,其他用户登录网页的时候,页面提示
400: Bad Request错误,大致如下所示:

[解决方法] 后来发现,是因为多个服务公用同一个域名导致在该域名下面存储的
Cookie太多导致的,可以设置如下两个参数来缓解该问题的出现。设置读取客户端请求头的缓冲区大小。对于大多数请求,
1K字节的缓冲区就足够了。但是,如果一个请求包含很长的cookie,或者来自WAP客户端,那么它可能不适合1K。如果请求行或请求头字段不适合这个缓冲区,那么将分配更大的缓冲区,由large_client_header_buffers指令配置。

- 设置用于读取大型的客户端请求头的缓冲区的最大数量和大小。请求行不能超过一个缓冲区的大小,否则会向客户端返回
414(Request-URI Too Large)错误。请求头字段也不能超过一个缓冲区的大小,否则会将400(Bad Request)错误返回给客户端,缓冲区仅按需分配。默认情况下,缓冲区大小等于8K字节。 如果在请求处理结束后将连接转换为保持活动状态,则会释放这些缓冲区。

- 后来网上找到了,对
nginx处理header时的方法,大致如下所示:- 1.先处理请求的 request_line,之后才是
request_header。 - 2.这两者的
buffer分配策略相同。 - 3.先根据
client_header_buffer_size配置的值分配一个buffer,如果分配的buffer无法容纳request_line/request_header,那么就会再次根据large_client_header_buffers配置的参数分配large_buffer,如果large_buffer还是无法容纳,那么就会返回414/400错误。
- 1.先处理请求的 request_line,之后才是
- 根据对手册的理解,这两个指令在配置
header_buffer时的使用场景是不同的,个人理解如下:- 1.如果你的请求中的
header都很大,那么应该使用client_header_buffer_size,这样能减少一次内存分配。 - 2.如果你的请求中只有少量请求
header很大,那么应该使用large_client_header_buffers,因为这样就仅需在处理大header时才会分配更多的空间,从而减少无谓的内存空间浪费。
- 1.如果你的请求中的
2. 开启转发 Delete 方法
老版本 Nginx 默认不转发 put/delete 等方法
[问题起因] 使用
Nginx代理网盘服务,但是操作删除文件的时候,提示有问题。[解决方法]
ngx_http_dav_module模块是为通过WebDAV协议自动管理文件而设计的,该模块处理HTTP和WebDAV的请求方法PUT、DELETE、MKCOL、COPY和MOVE。默认情况下,该模块不会构建,需要使用编译参数-–with-http_dav_module开启。需要额外WebDAV方法的WebDAV客户端操作,在该模块中不会起作用。
# 示例配置
location / {
root /data/www;
client_body_temp_path /data/client_temp;
dav_methods PUT DELETE MKCOL COPY MOVE;
create_full_put_path on;
dav_access group:rw all:r;
limit_except GET {
allow 192.168.1.0/32;
deny all;
}
}
3. 配置多个 HTTPS 主机
在 Nginx 服务上如何在同一个 IP 上配置多个 HTTPS 主机呢?
- [问题起因] 最近有个需求,就是需要保证新旧域名同时运行。那么,这就需要对于
https的域名,在同一个IP上如何同时存在多个虚拟主机了。

- [解决方法]
Nginx支持SSL需要使用--http_ssl_module这个模块,如果使用的版本没有安装的话,则需要手动安装或者使用新版本。我们这里使用的Docker容器镜像,该版本默认是支持的。
# 手动编译安装SSL支持
$ ./configure --prefix=/usr/local/nginx \
--with-http_ssl_module \
--with-openssl-opt="enable-tlsext" ...
# 编译并安装
$ make && make install
# 查看版本是否支持SSL功能
root@7e201233b8e1:/# nginx -V | grep ssl
nginx version: nginx/1.19.10
built by gcc 8.3.0 (Debian 8.3.0-6)
built with OpenSSL 1.1.1d 10 Sep 2019
TLS SNI support enabled
server {
listen 443 ssl default_server;
server_name www.escapelife.site;
ssl_certificate "/xxx/escapelife_site.crt";
ssl_certificate_key "/xxx/escapelife_site.key";
location / {
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_pass http://172.16.123.100:8000;
}
}
server {
listen 443 ssl;
server_name www.escapelife.com;
ssl_certificate "/xxx/escapelife_com.crt";
ssl_certificate_key "/xxx/escapelife_com.key";
location / {
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_pass http://172.16.123.101:8000;
}
}
4. Nginx 实现协议自跳转
利用 Nginx 的 497 状态码实现 http 跳转 https
- [问题起因] 有一个需求,就是将
http的请求转发到https上面,我们需要怎么做呢?
# Nginx的497状态码定义
497 - normal request was sent to HTTPS
- [解决方法] 当部署的
Nginx网站只允许https协议访问的时候,我们可以利用error_page命令将497状态码的链接重定向到https的地址上面。因为使用http协议访问的时候,Nginx会报出497这个自定义的错误码,我们刚好利用了这点来完成配置的。http和https是tcp的上层协议,当Nginx服务器建立tcp连接后,根据收到的第一份数据来确定客户端是希望建立tls还是http。Nginx会判断tcp请求的首写节内容以进行区分,如果是0x80或者0x16就可能是ssl或者tls,然后尝试https握手。如果端口开启了https,但请求过来的并不是,会抛出一个http级别的错误。- 这个错误的状态码是
NGX_HTTP_TO_HTTPS,错误代码497,然后在返回response中会抛出一个400错误(因为497不是标准状态码,丢给浏览器也没有用),这时浏览器会显示 “400 Bad Request,The plain HTTP request wes sent to HTTPS port”。这样,可以对497进行路由处理,做302重定向
# 示例配置
server {
listen 443;
server_name www.escapelife.com;
ssl on;
ssl_certificate cert/test.pem;
ssl_certificate_key cert/test.key;
# 让http请求重定向到https请求
error_page 497 https://$host$uri?$args;
}
- 当然,我们这里还可以使用其他方式完成同样的目的,但是使用
497这个状态码,可以不用开放80端口,直接将其转发到https协议上面。
# nginx的rewrite方法
# 将所有http请求通过rewrite重写到https上
sever{
listen 80;
server_name www.escapelife.com;
rewrite ^(.*)$ https://$host$uri permanentl;
}
server {
listen 443;
server_name www.escapelife.com;
ssl on;
ssl_certificate cert/test.pem;
ssl_certificate_key cert/test.key;
......
}
5. 用 Nginx 伪装 SSH 端口
利用 Nginx Stream 模块把 SSH 藏在 443 端口
[问题起因] 虽然可以使用
fail2ban防火墙工具护体(每天auth.log里一堆垃圾试探),但是使用Stream模块加固SSH服务器的连接更上一层楼。[解决方法] 这样做的好处在于,当我们连接服务(
:ssh)的时候,需要经过443端口进入(可以屏蔽默认的22端口或自定义端口),使服务更加安全一些且不要再头痛烦人的嗅探工具了。
stream {
map $ssl_preread_server_name $name {
escapelife xtls;
www.escapelife http;
default ssh;
}
upstream xtls {
server localhost:8081;
}
upstream http {
server localhost:8080;
}
upstream ssh {
# 默认ssh连接会回落到default然后到本机22端口
server localhost:22;
# 你甚至可以在这里挂个openvpn实现自用梯子
}
server {
listen 443 reuseport;
listen [::]443 reuseport;
proxy_pass $name;
proxy_protocol on;
ssl_preread on;
}
}
6. 解决 WS 链接 400 问题
内部环境发现页面 websocket 连接错误
- [问题起因] 因为服务前后端之前使用
WebSocket进行通信,所以遇到了如下问题:WebSocket可以减小客户端与服务器端建立连接的次数,减小系统资源开销,只需要一次HTTP握手,整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的非状态性,服务端会一直与客户端保持连接,直到你关闭请求,同时由原本的客户端主动询问,转换为服务器有信息的时候推送。- 客户端的话,支持就很多了,比如
Chrome/Firefox/Safari等浏览器内置了JS语言的WebSocket客户端、微信小程序开发框架内置的WebSocket客户端等等
# 报错信息大致如下所示
WebSocket connection to 'ws://xxx' failed: Error during WebSocket handshake: Unexpected response code: 400

- [解决方法] 经查阅相关资料,在
nginx.conf配置文件中location加入如下参数,即可。
# Nginx从1.3版本开始支持WebSocket的
location /chat/ {
# 告诉Nginx使用HTTP/1.1通信协议,这是WebSocket必须要使用的协议
proxy_http_version 1.1;
# 告诉Nginx当它想要使用WebSocket时,升级响应HTTP请求
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://backend;
}
7. 用 Nginx 解决跨域问题
利用 Nginx Stream 模块把 SSH 藏在 443 端口
- [问题起因] 当发现请求出现
403跨域错误的时候,需要给Nginx服务器配置响应的header参数来解决。

- [解决方法] 之前介绍过什么是跨越问题,可以参考之前写的博文(HTTPS 跨越问题处理方法)。
- [1] Access-Control-Allow-Origin
- 服务器默认是不被允许跨域的
- 添加上述配置表示服务器可以接受的请求源
- 星号(
*)表示接受所有跨域的请求
- [2] Access-Control-Allow-Methods
- 设置允许的方法类型
- [3] Access-Control-Allow-Headers
- 设置当前请求
Content-Type的值被支持,一般包括Content-Type/Cache-Control等 - 因为
application/json格式的请求不属于MIME类型,需要先发送预检请求 - 预检请求会带上头部信息
Access-Control-Request-Headers: Content-Type: - 服务器返回的头部信息如果不包含则表示不接受非默认的
Content-Type会报错
- 设置当前请求
- [4] 给 OPTIONS 添加 204 的返回
- 按需添加,不涉及的话,可以不用配置,主要是为了让预检请求通过
- 为了处理在发送
POST请求时Nginx依然拒绝访问的错误 - 发送预检请求时,需要用到方法
OPTIONS,所以服务器需要允许该方法
- [1] Access-Control-Allow-Origin
# 可以加到server段中
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
# 复杂请求时需配置;让预检请求通过;在location中才能添加
if ($request_method = 'OPTIONS') {
return 204;
}
}
# 指定接受那个来源的网址
# http://www.ruanyifeng.com/blog/2016/04/cors.html
add_header Access-Control-Allow-Origin http://sh.xxx.com;
# 要把Cookie发到服务器要服务器同意
# 设置此配置Origin就不能设为星号,必须指定明确与请求网页一致的域名
add_header Access-Control-Allow-Credentials 'true';
- [补充知识]
CORS是一个W3C标准,全称是跨域资源共享(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。简单来说就是跨域的目标服务器要返回一系列的 Headers,通过这些 Headers 来控制是否同意跨域。CORS 提供的 Headers,在 Request 包和 Response 包中都有一部分。- 简单请求
- 请求方法是这三种方法(
HEAD/GET/POST)之一 - HTTP 的头信息不超出这几种字段(
Accept/Accept-Language/Content-Language/Content-Type) Content-Type限于application/x-www-form-urlencoded/multipart/form-data/text/plain
- 请求方法是这三种方法(
- 复杂请求
- 那种对服务器有特殊要求的请求
- 比如请求方法是
PUT、DELETE或OPTIONS的等 - 或者
Content-Type字段的类型是application/json的 - 非简单请求的
CORS请求会在正式通信之前增加一次HTTP查询请求,称为预检请求(用OPTIONS方法)
- 简单请求
# HTTP Response Header
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Access-Control-Expose-Headers
Access-Control-Max-Age
# HTTP Request Header
Access-Control-Request-Method
Access-Control-Request-Headers
8. 为浏览器创建安全环境
新的跨域策略:使用 COOP、COEP 为浏览器创建更安全的环境
- [问题起因] 前端界面报错,提示:
# browser network
Uncaught (in promise) ReferenceError: ShareArrayBuffer is not defined
- [解决方法] 需要在
Nginx配置里面新增如下header信息。
# 可以通过 COOP、COEP 来创建隔离环境
add_header 'Cross-Origin-Opener-Policy' 'same-origin';
add_header 'Cross-Origin-Embedder-Policy' 'require-corp';
# 需要注意的是如果提示如下信息,则需要使用https协议
The Cross-Origin-Opener-Policy header has been ignored, because the URL is origin was untrustworthy.
Is was defined either in the final response or redirect.
Please deliver the response using the HTTPS protocol.
You can also use the 'localhost' origin install.
# 所有跨域相关的名词列出来
COEP: Cross Origin Embedder Policy:跨源嵌入程序策略
COOP: Cross Origin Opener Policy:跨源开放者政策
CORP: Cross Origin Resource Policy:跨源资源策略
CORS: Cross Origin Resource Sharing:跨源资源共享
CORB: Cross Origin Read Blocking:跨源读取阻止
9. 负载均衡的排坑和思考
- [问题起因]
Nginx在1.13版本之后就可以支持grpc的负载均衡了,使用方法类似proxy_pass的语法,但是使用的过程中遇到短连接的问题。
# project stack
"grpc_message":"Received RST_STREAM with error code 1", "grpc_status":13
- [解决方法]
grpc是基于http2的,而http2就是长连接的设计,即单个连接可以多路复用(http1不支持)。知道在使用nginx proxy_pass upstream的时候,需要配置keepalive参数,不然nginx做负载均衡转发一律会按照短连接处理,没想到grpc upstream也要配置keepalive参数才行。需要注意的是keepalive是单个worker的连接池,毕竟nginx是多进程的,在nginx的架构模型下是不能连接共享的。
server {
listen 9000 http2;
server_name _;
location / {
grpc_pass grpc://grpc_servers;
}
}
upstream grpc_servers {
keepalive 2000;
server 100.100.100.100:8090;
server 100.100.100.101:8090;
}
10. 请求客户端响应超时
服务端请求之后,客户端响应超时,并非必现场景!
- [问题起因] 客户端请求某个服务的时候,当
POST请求上传数据量打的时候,就会概率发生响应超时的情况。如下所示,在第三个请求的时候,请求超过60s之后服务就失败了。虽然可以通过加重试来间接解决这个问题,但是并没有找到对应的问题原因。proxy_connect_timeout(60s)-> 后端服务器连接的超时时间 发起握手等候响应超时时间- 该指令设置与
upstream server的连接超时时间,有必要记住这个超时不能超过75秒 - 这个不是等待后端返回页面的时间,那是由
proxy_read_timeout声明的 - 如果你的
upstream服务器起来了,但是hanging住了,那么这个声明是没有用的
- 该指令设置与
proxy_read_timeout(60s)-> 该指令设置与代理服务器的读超时时间- 它决定了
nginx会等待多长时间来获得请求的响应 - 这个时间不是获得整个
response的时间,而是两次reading操作的时间
- 它决定了
proxy_send_timeout(60s)-> 这个指定设置了发送请求给upstream服务器的超时时间- 超时设置不是为了整个发送期间,而是在两次
write操作期间 - 如果超时后,
upstream没有收到新的数据,nginx会关闭连接
- 超时设置不是为了整个发送期间,而是在两次
# project stack
122.100.11.123 - "POST /api/v1/xxx/png/60895/xxx HTTP/1.1" 200 426 2003840 0.513 0.096 "-"
122.100.11.124 - "POST /api/v1/xxx/png/60896/xxx HTTP/1.1" 200 413 1868160 0.484 0.076 "-"
122.100.11.125 - "POST /api/v1/xxx/png/60897/xxx HTTP/1.1" 200 383 11265848 60.114 0.132 "-"
229.207.88.100 - "POST /api/v1/xxx/png/60875/xxx HTTP/1.1" 200 374 11350466 1.214 0.196 "-"
229.207.88.100 - "POST /api/v1/xxx/png/60876/xxx HTTP/1.1" 200 383 16885939 1.148 0.224 "-"
229.207.88.100 - "POST /api/v1/xxx/png/60877/xxx HTTP/1.1" 200 378 16439591 36.457 0.124 "-"
- [解决方法] 很奇怪,并没有发现关于
60的Nginx设置,所以必定是某些默认参数的默认值。有根据返回码408判断初始因为client_header_timeout和client_body_timeout这两个参数导致客户端请求超时了。我们可以看到,正常情况下响应都是非常快的,基本在60s之内完全可以返回的。后来发现,是因为上了防火墙,其拿到请求之后需要进行流量分析导致拿到请求半天不给你传header。有可能需要充值信仰了。。。。client_header_timeout(60s)-> 指定等待client发送一个请求头的超时时间- 仅当在一次
read中,没有收到请求头,才会算成超时 - 如果在超时时间内,
client没发送任何东西,nginx返回HTTP状态码408
- 仅当在一次
client_body_timeout(60s)-> 该指令设置请求体的读超时时间- 仅当在一次
read step中,没有得到请求体就会设为超时,超时后nginx返回HTTP状态码408
- 仅当在一次
keepalive_timeout(75s)-> 服务器将会在这个时间后关闭连接- 这个头能够让一些浏览器主动关闭连接,这样服务器就不必要去关闭连接了


server {
sendfile on;
client_header_timeout 240;
client_body_timeout 240;
}
11. 平台网络上传限制
有时内部为了安全,会设置一些网络限制,比如上传文件的大小之类的!
- [问题起因] 发现某个服务之前测试上传没有问题,但是后续其他人时候的时候却发现无法上传文件,排除之后提示如下报错信息:
# 报错信息
Request URL: http://localhost:8080/api/v1/xxx/60897/file
Request Method: POST
Status Code: 413 Request Entity Too Large
- [解决方法] 根据经验是服务器限制了上传文件的大小,对应
1MB以下的文件上传放行,大于则阻止。但是,排除服务本身的设置并没有相关的设置(1MB),所以排除是部署服务的网络平台进行了设置导致的。
# 修改配置
$ sudo vim /etc/nginx/nginx.conf
client_max_body_size 200m;
# 重启服务
$ sudo nginx -t
$ sudo nginx -s reload
12. 浏览器的缓存配置
有时内部为了安全,会设置一些网络限制,比如上传文件的大小之类的!
- [问题起因] 我们已经会遇到,某个服务更新完成之后,通过浏览器并没有看到对应新功能或新特性。反馈之后,对应的解决方法就是三板斧(清理浏览器缓存)。如果只是内部使用的话,那到没有什么大问题,但是如果该系统是面向用户的话,就非常影响用户体验。

- [解决方法] 解决办法可以很简单,就是直接全部禁止缓存(在
Nginx端配置),但是如果真的是这样的话,那缓存的意义何在?应该找到一种好的方式来解决这个问题,而不是暴力的一刀切。- 如果网页是通过
iframe嵌入的话,有可能是因为iframe中有缓存导致,可以通过在iframe的src url后面加个时间戳,这样可以保证每次拿到的都是最新的。 - 如果是直接访问的话,可以通过对浏览器
header设置针对html的缓存策略(在Nginx端配置),只对收的html页面不设置使用缓存。正常html拿了新的,那css/js的hash的路径都变了,会自动重新获取新的地址。
- 如果网页是通过
# 设置iframe缓存(加个时间戳)
<iframe src="https://www.escapelife.site?date=new Date()">
# 自定义的变量设置
map $sent_http_content_type $cache_control_type {
default "private";
~text/html "no-store, no-transform";
}
add_header Cache-Control $cache_control_type;
# 官方给出的缓存策略设置
# http://nginx.org/en/docs/http/ngx_http_headers_module.html
map $sent_http_content_type $expires {
default off;
text/html epoch;
text/css max;
application/javascript max;
application/pdf 42d;
~image/ max;
}
server {
listen 80;
server_name your-domain;
gzip on;
gzip_types text/plain application/xml text/css application/javascript;
location / {
expires $expires;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://127.0.0.1:3000;
}
}
13. 文件路径相关的匹配
很多细小的需要注意的点,在用到的时候起到了关键作用!
[问题起因] 在配置
Nginx文件目录映射的时候(主要是前端代码、图片资源等),需要将用户请求的文件映射到对应目录下面。但是却发现,配置的代码并没有在合适的地方找到,就很诡异。[解决方法] 最后发现是因为
root和alias指令用法不对导致的,这里简单记录下相关指令,以备后续查看和使用。
【1】root path;
- 作用域
http、server、location、if in location
- 含义解释
- 设置
Web资源路径,用于指定请求的根文档目录
- 设置
- 默认值
root html;
# /index.html将由/www/htdocs/index.html文件来响应
location / {
root /www/htdocs;
}
# /images/b.html将由/data/w3/images/b.html文件来响应
location /images/ {
root /data/w3;
}
【2】alias path;
- 作用域
location
- 含义解释
- 只能用于
location中,用于路径别名
- 只能用于
# /i/top.gif将由/data/w3/images/top.gif文件来响应
location /i/ {
alias /data/w3/images/;
}
# /images/b.html将由/data/w3/b.html文件来响应
location /images/ {
alias /data/w3/;
}
# /images/b.html将由/data/w3/images/b.html文件来响应
location /images/ {
alias /data/w3/images/;
}
location ~ ^/users/(.+\.(?:gif|jpe?g|png))$ {
alias /data/w3/images/$1;
}
【3】index file ...;
- 作用域
http、server、location
- 含义解释
- 定义默认页面,可参跟多个值,列表中的最后一个元素可以是一个带有绝对路径的文件
- 模块
ngx_http_index_module处理以斜线字符/结尾的请求
- 默认值
index index.html;
location / {
index index.$geo.html index.0.html /index.html;
}
# 需要注意的是,index文件会引发内部重定向,请求可能会被其它location处理
# 如下面这个例子中,请求“/”实际上将会在第二个location中作为“/index.html”被处理
location = / {
index index.html;
}
location / {
...
}
【4】error_page code ... [=[response]] uri
- 作用域
http、server、location、if in location
- 含义解释
- 定义错误页面重定向
- 当对于某个请求返回错误时,如果匹配上了
error_page指令中设定的code,则重定向到新的URI中
# 一般的设置方式
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
# 可以使用=response语法改变响应状态码,响应的如果是404的话返回给用户200页面
error_page 404 =200 /empty.gif;
# 也可以使用本指令对错误处理进行重定向
error_page 403 http://example.com/forbidden.html;
error_page 404 =301 http://example.com/notfound.html;
# 如果内部跳转时无需改变URI,可以将错误处理转到一个命名路径
location / {
error_page 404 = @fallback;
}
location @fallback {
proxy_pass http://backend;
}
【5】try_files file ... uri;
- 作用域
server、location
- 含义解释
- 还有一种表达方式:
try_files file ... =code; - 自左至右尝试读取指定的路径,在第一次找到即停止并返回,如果所有路径均不存在,则返回最后一个
URI - 文件路径是根据
root指令和alias指令,将file参数拼接而成 - 可以在名字尾部添加斜线以检查目录是否存在,比如
$uri/
- 还有一种表达方式:
# $uri表示请求的路径
location /images/ {
try_files $uri /images/default.gif;
}
location = /images/default.gif {
expires 30s;
}
# /documents/a.html请求,先查找/docu/a.html,在查找/temp.html
location ~* ^/documents/(.*)$ {
root /www/htdocs;
try_files $uri /docu/$1 /temp.html;
}
# 从0.7.51版本开始,最后一个参数也可以是code
location / {
try_files $uri $uri/index.html $uri.html =404;
}
# 下面是代理Mongrel的例子
location / {
try_files /system/maintenance.html
$uri $uri/index.html $uri.html
@mongrel;
}
location @mongrel {
proxy_pass http://mongrel;
}
# 下面是Drupal用FastCGI的例子
location / {
try_files $uri $uri/ @drupal;
}
location ~ \.php$ {
try_files $uri @drupal;
fastcgi_pass ...;
fastcgi_param SCRIPT_FILENAME /path/to$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param QUERY_STRING $args;
... other fastcgi_param's
}
location @drupal {
fastcgi_pass ...;
fastcgi_param SCRIPT_FILENAME /path/to/index.php;
fastcgi_param SCRIPT_NAME /index.php;
fastcgi_param QUERY_STRING q=$uri&$args;
... other fastcgi_param's
}
14. 协议转换导致 Post 请求丢失
http 转发成 https 导致 post 请求变为 get 请求!
- [问题起因] 越来越多的网站都从
http迁移到了https协议。但迁移到https之前分发出去的链接都是http的,为了兼容以前的链接,就需要做一个redirect,将http的请求redirect到https中。

- [解决方法] [方法一] 使用
301 Moved Permanently将http重定向到https中。- 对于普通的
GET请求,这种方式是完全可以的。
- 对于普通的
server {
listen 80;
server_name *.xxx.com;
return 301 https://*.xxx.com;$request_uri;
}
server {
listen 443 ssl default_server;
server_name *.xxx.com;;
......
}
- [解决方法] [方法二] 所有
Post请求通过307再转发就不会修改成get请求。- 但当请求是
POST的时候,由于一些历史原因,一些浏览器和库收到301/302时会将原本的POST请求转为GET请求发送。此时POST的body会丢失,导致请求出问题。这时就需要,使用307 Temporary Redirect来进行解决。
- 但当请求是
# nginx 307
# This status code is similar to 302 (Found), except that it does not allow changing the request method from POST to GET.
sever {
listen 80;
server_name *.xxx.com;
if ($request_method ~* POST) {
return 307 https://$server_name$request_uri;
}
rewrite ^(.*) https://$host$1;
}
server {
listen 443 ssl default_server;
server_name *.xxx.com;;
......
}
15. 前置 F5 设备过滤请求
F5 可以通过配置或者默认(未识别)过滤服务请求!
- [问题起因] 部署服务反馈页面访问均正常,但是反馈无法新建和其他特定操作,请求的页面显示如下所示。

- [解决方法] 登录服务器查看对应日志发现,服务并没有收到任何的
POST请求,判断请求就没有到该服务上,有可能被前置服务拦截或者过滤了。后来发现,服务是进行数据加密传输的,可能会因为格式等问题,会被F5(不认识)拦截。放开加密配置,服务即正常可用。
16. 配置浏览器版本选择支持
使用 map 指令配置服务只支持>=93 版本的 chrome 浏览器
[问题起因] 典型的示例 - 配置服务只支持
>=93版本的chrome浏览器- 获取变量
A的内容进行匹配 - 如果匹配到固定浏览器版本则将其置为后面的值
- 如果没有匹配到的话,默认值为
0
- 获取变量
[解决方法]
map指令简单使用示例。
# A(原变量) B(自定义变量)
map $http_user_agent $unsupport_browser {
default 1;
"~Chrome/[0-8][0-9]?\." 1;
"~Chrome/9[0-2]?\." 1;
"~Chrome" 0;
"~Chromium/[0-6][0-9]?\." 1;
"~Chromium/7[0-5]?\." 1;
"~Chromium" 0;
}
server {
listen 8080;
server_name _;
if ($unsupport_browser = 1) {
rewrite .*\.html /unsupported_browser.html last;
}
......
}
17. Nginx 日志写错文件
具体问题值得深究一下
通过 logrotate 工具触发 Nginx 的日志轮转,因为是 Ubuntu 系统,所以对应的配置如下所示:
- 创建
/etc/logrotate.d/nginx的文件,用于指定Nginx日志轮转。
# ubuntu
$ sudo vim /etc/logrotate.d/nginx
- 添加配置文件内容。
*.log-> 指定要轮转的Nginx日志文件daily-> 设置日志轮转的时间间隔为一天(weekly/monthly/yearly)missingok-> 如果日志文件不存在则忽略它并继续运行rotate 60-> 保留60个轮转后的日志文件delaycompress-> 在轮转压缩之前保持一个旧的未压缩的文件notifempty-> 在日志文件为空时不轮转该文件create-> 创建新的日志文件使用的访问权限和用户组sharedscripts-> 在运行指定命令之前和之后只运行一次postrotate命令prerotate-> 执行轮命令之前的预处理操作postrotate-> 重新打开Nginx日志文件进行日志轮转
# 配置文件格式
/var/log/nginx/*.log {
daily
missingok
rotate 60
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi \
endscript
postrotate
invoke-rc.d nginx rotate >/dev/null 2>&1
endscript
}
# 手动强制轮转
$ sudo invoke-rc.d nginx rotate
我们会在其他系统上面看到,postrotate 字段可能是 kill -USR1 的形式,两个命令实现的功能是相同的,都用于手动触发 Nginx 日志轮转。不同的是,前者是使用 Debian 工具来执行轮转,而后者是直接使用 Linux shell 命令进行操作。
invoke-rc.d nginx rotateinvoke-rc.d-> 用于执行Debian系统服务控制脚本的工具nginx-> 要执行操作的服务名称rotate-> 轮转操作的指令参数>/dev/null 2>&1-> 不输出任何日志内容
kill -USR1 $(cat /var/run/nginx.pid)[ -f /var/run/nginx.pid ]-> 检查Nginx进程是否在运行kill -USR1-> 向Nginx进程发送USR1信号以触发日志轮转操作USR1-> 是一个自定义的信号,表示Nginx收到这个就会触发轮转
18. Nginx 配置 WS 请求
分享一个把 ws 和 http 放到一个 location 的方法
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
location / {
proxy_http_version 1.1;
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $http_scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://127.0.0.1:8000;
}
}