这里主要是为了记录在使用 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 rotate
invoke-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;
}
}