Nginx 相关 

Last Update: 2023-07-22

目录

踩坑

缺少模块

FreeBSD 上 (截止到 2021.9.13 FreeBSD 13) 没有 ngx_stream_proxy_module 这个模块,所以 proxy protocol 这个特性没法在 FreeBSD 上用。

HTTP 重定向到 HTTPS 的配置导致浏览器报重定向次数过多

如果用了 Cloudflare 的 DNS 解析服务的话,那出现这个问题的原因可能是 Cloudflare 的 SSL/TLS 配置没有选对。

打开 Cloudflare 域名配置的 SSL/TLS 配置下的 Overview 选项卡后,可以看到当前域名下的 SSL/TLS 加密配置,有以下几个选项:

浏览器报重定向次数过多的原因是重定向规则有环。如果 Cloudflare 的 SSL/TLS 加密配置用了 Flexible ,那么 Cloudflare 到 Nginx 的数据就不被加密。这意味着,Cloudflare 会使用 HTTP 请求 Nginx 的 80 端口。但是 Nginx 里面的配置又写了 80 端口的 HTTP 被重定向到 HTTPS,这时候 Cloudflare 解析出重定向规则后就会告诉浏览器重新用 HTTPS 请求该地址,而该地址本身就是以 HTTPS 请求发到 Cloudflare 的,就是这样的环:

        https://lishouzhong.com                http://lishouzhong.com
浏览器 -------------------------> Cloudflare ------------------------> lishouzhong.com
^--------------------------------------------------------------------------------|
                            301/302 Redirect
                    Location: https://lishouzhong.com

无法上传大文件

遇到的问题是,Nginx 为 Gitea 提供反向代理,在向 Gitea 推送 commit 时,如果推送的 commit 中包含了大文件会出现无法推送的情况。

原因是 nginx 默认只允许 1M 大小的上行数据,即上传文件或者接收请求最大请求包不能超过 1M。要解决这个问题,需要修改 nginx 配置文件,可以在下列位置重新做限制:

http {
  client_max_body_size 50M;
  ...
  server {
    client_max_body_size 50M;
    ...
    location {
      ...
      client_max_body_size 50M;
    }
  }
}

基础

3 个主要应用场景:

  1. 静态资源服务。
  2. 反向代理: 缓存和负载均衡。
  3. API 服务: OpenResty。

4 个主要组成部分:

  1. 二进制可执行文件。
  2. nginx.conf 配置文件。
  3. access.log 访问日志。
  4. error.log 错误日志。

5 个主要优点:

  1. 高并发,高性能。
  2. 扩展性强。
  3. 可靠性高。
  4. 可以热部署: 在不停止服务的情况下升级或更改配置。
  5. 使用 BSD 许可证。

进程控制信号

可用命令 nginx -s 发送的信号:

只能用 kill 命令发送的信号:

reload 命令执行流程

  1. 向 master 进程发送 HUP 信号;
  2. master 进程校验配置文件语法是否正确;
  3. master 进程打开新的监听端口;
  4. master 进程用新配置启动新 worker 子进程;
  5. master 进程向旧 worker 子进程发送 QUIT 信号;
  6. 旧 worker 子进程关闭监听句柄,处理完当前连接后结束。

worker 进程关闭流程

  1. 设置定时器 worker_shutdown_timeout ,这个属性在 conf 文件里设置;
  2. 关闭监听句柄;
  3. 关闭空闲连接;
  4. 在循环中等待全部连接关闭;
  5. 退出进程。

热更新流程

  1. 旧二进制文件备份,新二进制文件代替旧二进制文件。
  2. 给旧二进制文件对应的 master 进程发送信号 kill -USR2 <old_pid>:
    1. 这个信号会新让旧 master 进程启动一个新 master 进程。
    2. 旧 master 进程不会关闭,而且它虽然在继续监听 80 443 等端口,但因为旧进程的 fd 已经从 epoll 中移出,所以实际上它并不处理新连接。
    3. 旧 master 是新 master 的父进程,所以新 master 能共享打开的监听端口。
  3. 给旧的 master 进程发送信号 kill -WINCH <old_pid>:
    1. 通知旧的 master 进程平滑关闭自己的 worker 进程。
    2. 保留旧版本的 master 是为了方便回滚,也可以发信号 QUIT 或者直接杀掉进程。
  4. 更新完成。

版本回滚流程

  1. 用旧的二进制文件覆盖新的二进制文件。
  2. 向旧 master 进程发信号 kill -HUP <old_pid>:
    1. 相当与 nginx -s reload 指令的作用,把旧 nginx 的worker进程拉起来,但这里并不直接使用 reload 的方式执行。
    2. 它会在没有旧 worker 进程时启动 worker 进程,这些进程属于旧 master 进程的子进程。
  3. 给新 master 进程发送信号 kill -USR2 <new_pid>:
    1. 此时,接收用户请求的是旧版本的nginx进程。
    2. 新版本的nginx进程不再接受用户请求。
  4. 回滚完成。可以关闭新的 master 进程。

日志切分

直接备份原本的日志文件后,执行 nginx -s reopen 会使 nginx 重新生成日志文件。

证书相关

SSL 证书更新后,需要执行 nginx -s reload 让 nginx 重新读取配置文件才能使用新的证书。

内置模块

时间配置参数:

http_core 模块 location 路径匹配

nginx 的 location 块的语法有两种写法:

location [ = | ~ | ~* | ^~ ] <uri> { ... }
location @name { ... }

第一部分参数根据检索顺序进行说明:

Search-OrderModifierDescriptionMatch-TypeStops-search-on-match
1st=The URI must match the specified pattern exactlySimple-stringYes
2nd^~The URI must begin with the specified patternSimple-stringYes
3rd(None)The URI must begin with the specified patternSimple-stringNo
4th~The URI must be a case-sensitive match to the specified RxPerl-Compatible-RxYes (first match)
5th~*The URI must be a case-insensitive match to the specified RxPerl-Compatible-RxYes (first match)
N/A@Defines a named location block.Simple-stringYes

location 块的 uri pattern 也支持 Rx Capturing-group (正则表达式捕获组),且 Rx 内部的 () 默认为捕获组模式,使用 (?:) 可以关闭捕获组模式。比如 (?:a|b) 意为以非捕获组模式对 a|b 进行匹配。

比如 location ~ ^/(?:index|update)$ 可以匹配 example.com/indexexample.com/update

# -------------------------------------------------------------------------------------
#  ()  : Group/Capturing-group, capturing mean match and retain/output/use what matched
#        the patern inside (). the default bracket mode is "capturing group" while (?:)
#        is a non capturing group. example (?:a|b) match a or b in a non capturing mode
# -------------------------------------------------------------------------------------
#  ?:  : Non capturing group
#  ?=  : Positive look ahead
#  ?!  : is for negative look ahead (do not match the following...)
#  ?<= : is for positive look behind
#  ?<! : is for negative look behind
# -------------------------------------------------------------------------------------

正向斜杠 / 在 nginx 中没有特殊含义,比如, location / 可以匹配任以路径。而反斜杠 \ 为转译字符。

# -------------------------------------------------------------------------------------
#   /  : It doesn't actually do anything. In Javascript, Perl and some other languages,
#        it is used as a delimiter character explicitly for regular expressions.
#        Some languages like PHP use it as a delimiter inside a string,
#        with additional options passed at the end, just like Javascript and Perl.
#        Nginx does not use delimiter, / can be escaped with \/ for code portability
#        purpose BUT this is not required for nginx / are handled literally
#        (don't have other meaning than /)
# -------------------------------------------------------------------------------------

nginx 支持 Perl-Compatible-Rx:

# -------------------------------------------------------------------------------------
#   ~   : Enable regex mode for location (in regex ~ mean case-sensitive match)
#   ~*  : case-insensitive match
#   |   : Or
#   ()  : Match group or evaluate the content of ()
#   $   : the expression must be at the end of the evaluated text
#         (no char/text after the match) $ is usually used at the end of a regex
#         location expression.
#   ?   : Check for zero or one occurrence of the previous char ex jpe?g
#   ^~  : The match must be at the beginning of the text, note that nginx will not perform
#         any further regular expression match even if an other match is available
#         (check the table above); ^ indicate that the match must be at the start of
#         the uri text, while ~ indicates a regular expression match mode.
#         example (location ^~ /realestate/.*)
#         Nginx evaluation exactly this as don't check regexp locations if this
#         location is longest prefix match.
#   =   : Exact match, no sub folders (location = /)
#   ^   : Match the beginning of the text (opposite of $). By itself, ^ is a
#         shortcut for all paths (since they all have a beginning).
#   .*  : Match zero, one or more occurrence of any char
#   \   : Escape the next char
#   .   : Any char
#   *   : Match zero, one or more occurrence of the previous char
#   !   : Not (negative look ahead)
#   {}  : Match a specific number of occurrence ex. [0-9]{3} match 342 but not 32
#         {2,4} match length of 2, 3 and 4
#   +   : Match one or more occurrence of the previous char
#   []  : Match any char inside
# ------------------------------------------------------------------------------------

http_limit 限制访问频率

ngx_http_limit_req_module 限制 HTTP 请求频率,采用漏桶算法。

ngx_http_limit_conn_module 限制 TCP 并发连接数。

两个模块都基于 IP 来限制访问频率。

限制 HTTP 请求频率

ngx_http_limit_req_module 使用 limit_req_zonelimit_req 配合达到频率限制效果。一段时间内的,来自单个 IP 的 HTTP 请求如果超过指定数量,nginx 就返回 503 状态码 (通常会改为 429 Too Many Requests)。

ngx_http_limit_req_module 限制某段时间内同一 IP 访问频率:

http{
    ...
    limit_req_zone $binary_remote_addr zone=httplimit:10m rate=20r/s;
    limit_req_status 429;
    ...
    server{
        ...
        limit_req zone=httplimit burst=10 nodelay;
        ...
    }
    ...
}

注意: 限速 20r/s 的意义是,每 50ms 只处理一个请求。即,假设 1s 内只有两个请求,而这两个请求都在 50ms 内到达,那么第二个到来的请求会被丢弃 (或者进入 burst 队列)。

给一个例子帮助理解。假设 1s 内只有 3 个请求,而这 3 个请求都在 50ms 内到达。那么,第一个到达的请求被正常处理,第二三个到达的请求进入 buster 队列,如果:

限制 TCP 并发连接数

ngx_http_limit_conn_module 限制单个 IP 的 TCP 并发连接数:

http{
    ...
    limit_conn_zone $binary_remote_addr zone=tcplimit:10m;
    limit_conn_status 429;
    ...
    server{
        ...
        limit_conn tcplimit 40;
        limit_rate 500K;
        ...
    }
    ...
}

http_gzip 压缩数据包大小

gzip 配置的常用参数:

给一个常用配置:

# gzip
gzip                on;
gzip_buffers        16 8K;
gzip_comp_level     6;
gzip_min_length     100;
gzip_types          *; # compress all MIME type files
gzip_disable        "MSIE [1-6]\."; # ie6 and earlier version do not suport gzip
gzip_vary           on;

注: 图片或者 mp3 这样的二进制文件的压缩率较小,耗费 CPU 资源较多,所以这类文件也可以不压缩。

http_proxy 转发与代理

proxy_cache 缓存

缓存的不活跃和过期

需要注意: inactive 计时器走完,缓存会被删除,而 proxy_cache_valid 计时器走完后,缓存不被删除。

只要有请求出现,inactive 计时器就被刷新,重新开始计时。而不论有没有请求进入, proxy_cache_valid 计时器都不会被刷新,计时不会被打断。而一直没请求出现的话, inactive 和 proxy_cache_valid 的计时器都不会被刷新。

如果这两个配置项同时被启用,则会有如下的情况:

根据上面的分析可以得出一个结论: 使用 proxy_cache_valid 的目的就在于设置一个强制刷新缓存的频率

在不配置 proxy_cache_valid 的情况下,如果某个缓存被频繁访问,那么就会导致 inactive 计时器不断被刷新,而 inactive 计时器不结束的话,nginx 就不会更新这个被频繁访问的缓存。如果被缓存的数据已经被更新了,由于 inactive 计时器一直没有结束,新数据无法进入缓存,那么更新后的数据无法被任何人访问到。

第三方模块

可以静态编译进 Nginx 二进制文件,也可以编译成库文件由 Nginx 挂载。

推荐把第三方模块编译成库文件,这样可以正常从包管理软件更新 Nginx 版本。

Brotli 压缩

Brotli 是谷歌开源的比 gzip 更高效的压缩算法。

Linux 系统

git clone https://github.com/google/ngx_brotli.git --recursive 从 Github 下载 ngx_brotli 源代码。

nginx -v 查看 Nginx 的版本,去官网下载对应的版本。解压到 ngx_brotli 同级的文件夹。

进入 Nginx 源码文件夹,执行:

./configure --with-compat --add-dynamic-module=../ngx_brotli

按照提示,安装上缺少的依赖。然后执行 make modules 开始编译库文件。

代码跑完后,Nginx 源码文件夹下会出现一个 objs 文件夹,里面有 ngx_http_brotli_static_module.songx_http_brotli_filter_module.so 两个库文件。

此时需要注意: 如果使用 Debian 11 及以上系统,系统默认装了 libbrotli1 这个 shared library ,它包含了 ngx_http_brotli_filter_module 。Debian 10 及以下系统默认没装这个包。

所以 Debian 11 及以上版本系统只需要在 Nginx 配置文件里写明加载 /ngx_http_brotli_static_module.so/ 即可。Debian 10 及以下需要加载两个 .so 库文件。

BSD 系统

如果系统是 FreeBSD,可以在 /usr/ports/www/nginx 使用 Ports 编译。编译后可以在 /usr/ports/www/nginx/work 文件夹中找到编译好的源码文件夹。

在编译好源码之后,把在源码包的 objs 文件夹中找到的以 .so 结尾的库文件复制到 Nginx 的动态库文件夹,再在配置文件里加载模块即可。

给一个常用配置:

# brotli
brotli              on;
brotli_comp_level   6;
brotli_buffers      16 8k;
brotli_min_length   1k;
brotli_types        *;