nginx 介绍

nginx [engine x] 是一款 HTTP 和反向代理服务器、邮件代理服务器和通用 TCP/UDP 代理服务器,最初由 Igor Sysoev 编写。根据 Netcraft 提供的数据,截止2022年7月,nginx 服务或者代理了 21.55% 最繁忙的站点,包括 Dropbox、Netflix、Yandex 等知名网站和应用。nginx 的源代码和文档以 2-clause BSD 许可分发,对开发者十分友好。

nginx 最常用的功能之一是作为 HTTP 服务器使用,nginx 可以提供的 HTTP 服务器功能包括:

  1. 提供静态文件路由、文件描述符缓存
  2. 带缓存的加速反向代理、负载均衡和容错
  3. 模块化结构。支持包括 gzipping 、XSLT 、SSI 和 图像转换 过滤
  4. SSL 和 TLS 支持
  5. 支持 HTTP/2
  6. 基于名称和基于 IP 的虚拟服务器
  7. 基于 客户端IP地址、 密码(HTTP基本认证)和 子请求结果 的访问控制
  8. 验证 HTTP referer
  9. 限制 来自一个地址 的同时连接 或 请求的数量
  10. 基于IP的地理定位
  11. A/B 测试

可以看到 nginx 是十分强大的,支持 HTTP 服务器的几乎所有功能,而且 nginx 的配置也十分简单灵活,支持的操作系统亦十分广泛,包括 FreeBSD、Linux、Solaris、macOS、Windows Server 等常见的服务器系统都支持。

nginx 有一个主进程和几个工作进程。主进程的主要目的是读取和评估配置,并维护工作进程。工作进程对请求进行实际处理。nginx 采用基于事件的模型和依赖于操作系统的机制来有效地在工作进程之间分配请求。工作进程的数量在配置文件中定义,并且可以针对给定的配置进行固定或自动调整为可用 CPU 内核的数量。

nginx 及其模块的工作方式在配置文件中确定。默认情况下,配置文件被命名为 nginx.conf 并放置在目录 /usr/local/nginx/conf/etc/nginx/usr/local/etc/nginx 中。

nginx 安装

nginx 的安装过程如下,以 macOS 为例:

  1. 通过 Homebrew 安装
1
brew install nginx

nginx 默认被安装在 /usr/local/Cellar/nginx/
conf文件默认被安装在 /usr/local/etc/nginx/nginx.conf

  1. 将 nginx 命令添加到系统环境变量 Path 中
1
export PATH="$PATH:/usr/local/Cellar/nginx/1.23.1/bin"
  1. 开启 nginx
1
nginx
  1. 访问 Web 页面

nginx 默认开启 8080 端口的 http 服务,可以通过 http://localhost:8080 访问 nginx 的默认页面。默认页面的位置在 /usr/local/Cellar/nginx/1.23.1/html/index.html

  1. 查看 nginx 日志

日志存放在 /usr/local/var/log/nginx/*.log,其中 access.log 是访问日志,error.log 是错误日志。

nginx 命令

要启动 nginx,直接运行 nginx 可执行文件即可,nginx 一旦启动,就可以通过 -s 参数调用可执行文件来控制它。

1
nginx -s [signal]

signal 包括:

  • stop 快速关闭
  • quit 优雅的关闭
  • reload 重载配置文件
  • reopen 重新打开日志文件

执行 ps -ax | grep nginx 可以查看所有正在运行的 nginx 进程的列表。

nginx 的主进程 ID 默认记录在 /usr/local/nginx/logs/nginx.pid

nginx 配置

nginx.conf 配置文件的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
# access_log logs/access.log main;
keepalive_timeout 65;
server {
listen 8080 backlog=4096;
server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;

location / {
root html;
index index.html index.htm;
}
#...
}
include servers/*;
}

nginx 配置由模块(moudle)组成,这些模块由配置文件中指定的指令(directive)控制。指令分为 简单指令块指令 。一个简单的指令由 名称参数 组成,由空格分隔并以分号结尾。块指令与简单指令具有相同的结构,但它不是以分号结尾,而是以一组由大括号包围的附加指令。如果块指令可以在大括号内包含其他指令,则称为上下文(context)(例如: events、 http、 server 和 location)。
放置在任何上下文之外的配置文件中的指令被认为是在 主上下文(main context) 中。例如,eventshttp 指令包括在 主上下文 中,server 指令在 http 中,而 locationserver 中。# 符号之后的是注释。

nginx 处理连接的速度非常快,一般规则是当连接建立时,它被放入监听套接字的“监听”队列中。

显示当前监听队列:

1
netstat -Lan

结果类似如下:

1
2
3
4
5
Current listen queue sizes (qlen/incqlen/maxqlen)
Listen Local Address
0/0/128 *.12345
10/0/128 *.80
0/0/128 *.8080

为了获得最佳性能,需要增加在操作系统和 nginx 配置中排队等待 nginx 接受的最大连接数,将 net.core.somaxconn 内核参数的值从其默认值 (128) 增加到一个足够高的值以应对大量流量。

对于 FreeBSD,运行命令 sudo sysctl kern.ipc.somaxconn=4096;对于 Linux,运行命令 sudo sysctl -w net.core.somaxconn=4096,使用文本编辑器将 net.core.somaxconn = 4096 添加到 /etc/sysctl.conf 。同时,将 listen 指令的 backlog 参数改为匹配的最大连接数。

nginx 功能

静态资源访问

nginx 最主要的一个功能是提供静态内容的访问。nginx 通过 指令匹配 来指定返回的内容,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
http {
server {
location / {
root /data/www;
index index.html index.htm;
try_files $uri $uri/ $uri.html /index.html =404;
}
location ~ \.(gif|jpg|png|jpeg|svg)$ {
root /data/images;
valid_referers none blocked server_names *.example.com ~\.google\.;
if ($invalid_referer) {
rewrite ^/ http://www.example.com/403.html break;
# return 403;
}
}
location /directory/ {
root /data/directory;
autoindex on;
}
location /mp3 {
sendfile on;
sendfile_max_chunk 1m;
}
location /wrong/url {
return 404;
}
location /permanently/moved/url {
return 301 http://www.example.com/moved/here;
}
location /old/path.html {
error_page 404 =301 http://www.example.com/new/path.html;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
}
}

第一个 location 块指令指定 / 前缀 与来自请求的 URI 相匹配。如果匹配上了,那么 URI 将添加到 root 指令中指定的路径,即 /data/www,以形成本地文件系统上请求文件的路径。例如,URI 请求了 /index.html,与 / 匹配,那么就会返回本地的 /data/www/index.html 资源。如果有多个匹配的 location 块,nginx 会选择具有最长前缀的块。上面的第一个 location 块提供了最短的前缀,长度为 1,因此只有当所有其他 location 块都无法提供匹配时,才会使用这个块。
第二个 location 块指令以 ~ 开始是一个正则表达式匹配。它可以匹配所有 以 .gif,.jpg 或 .png 结尾的 URI。

location 指令支持 前缀字符串(路径名)和 正则表达式 两种匹配方式。前缀字符串匹配必须以前缀字符串开头,正则表达式以 ~ 或者 ~* (表示不区分大小写)开头。nginx 对正则表达式使用 Perl 语法。如果请求以斜线结尾,nginx 会将其视为对目录的请求,并尝试在目录中查找索引文件。

root 指令指定将用于搜索文件的根目录。

index 指令定义索引文件的名称(默认值为 index.html ),可以在指令中列出多个文件,nginx 以指定的顺序搜索文件并返回它找到的第一个。

valid_referers 指令用于根据请求 Referer 的内容对请求进行限制,通常用来进行静态资源的防盗链。参数 none 表示 Referer 不存在(从浏览器地址栏直接打开链接时的情况);参数 blocked 表示 Referer 在请求头中存在,但是被防火墙或代理服务器删除了(例如值是字符串但并不是以 http://https:// 开头);参数 server_names 表示 Referer 应当包含列出的服务器之一,列出的服务器地址支持以 * 通配符来匹配,或者以 ~ 开头的正则表达式。当匹配不通过时,$invalid_referer 变量的值将为 1,可以通过该字段来进行之后的处理。

autoindex 指令表明 nginx 配置为返回自动生成的目录列表。

try_files 指令用于检查指定的文件或目录是否存在,nginx 会进行内部重定向,否则会返回指定的状态码。

默认情况下,nginx 自己处理文件传输,并在发送之前将文件复制到缓冲区中。sendfile 指令消除了将数据复制到缓冲区的步骤,并允许将数据从一个文件描述符直接复制到另一个文件描述符。sendfile_max_chunk 指令来限制在单个 sendfile() 调用中传输的数据量,防止一个快速连接完全占用工作进程。

return 指令用于返回特定的响应码。对于某些响应码,例如 301 重定向响应,可以通过额外的参数来指定重定向的地址。

rewrite 指令用于重定向。当指定的正则表达式与请求 URI 匹配时,URL 将按照 rewrite 指定的字符串进行更改。第一个参数为 URI 需要匹配的正则表达式,第二个参数为替换的字符串,第三个参数为可选的标志参数,可以为 last 表示停止当前 redirect 指令的匹配,开始新的 location 匹配; break 表示停止当前的 redirect 指令的匹配,在此位置继续对请求进行进一步处理; redirect 表示返回 302 响应码; permanent 表示返回 301 响应码。

error_page 指令用于对特定的响应码返回相应的内容。

代理请求(请求转发)

nginx 的常见用途之一是将其设置为请求代理,这意味着 nginx 服务器接收请求,将请求传递给代理服务器,然后 nginx 服务器从代理服务器接受响应,并将响应发送给客户端。

1
2
3
4
5
6
7
8
server {
listen 8080;
root /data/up1;

location / {

}
}

请注意,该 root 指令放置在 server 上下文中,这样,当 location 块没有提供自己的 root 指令时就会直接使用 server 上下文中的 root。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
listen 80;
location / {
proxy_pass http://localhost:8080;
proxy_redirect off;
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 Accept-Encoding "";
}
location /some/path1/ {
proxy_buffers 16 4k;
proxy_buffer_size 2k;
proxy_pass http://localhost:8000;
proxy_bind $server_addr;
}
location /some/path2/ {
proxy_buffering off;
proxy_pass http://localhost:8000;
proxy_bind 127.0.0.1;
}
}

在 location 块中添加 proxy_pass 指令,就可以将请求转发到对应的地址。如上,就是将 http://localhost:80 的请求转发到了 http://localhost:8080 上。

nginx 也支持将请求传递给非 HTTP 代理服务器,**_pass 应使用适当的指令:

  • fastcgi_pass 将请求传递给 FastCGI 服务器
  • uwsgi_pass 将请求传递给 uwsgi 服务器
  • scgi_pass 将请求传递给 SCGI 服务器
  • memcached_pa​​ss 将请求传递给了一个 memcached 服务器

在代理请求时,默认情况下,nginx 会重新定义代理请求中的两个 header 字段,“Host” 和 “Connection”,并消除值为空字符串的 header 字段。“Host” 设置为 $proxy_host 变量,“Connection” 设置为 close。

proxy_set_header 指令可以更改默认的 header 设置以及修改其他 header 字段。要防止标头字段被传递到代理服务器,可以将其设置为空字符串。

proxy_buffering 用于启用和禁用缓冲。默认情况下,nginx 缓冲来自代理服务器的响应,这有助于优化慢速客户端的性能。proxy_buffers 指令控制为请求分配的缓冲区的数量和大小。来自代理服务器的响应的第一部分存储在单独的缓冲区中,其大小由proxy_buffer_size 指令设置。

proxy_bind 指令用于指定代理服务器的 IP 地址。IP 地址也可以用变量指定,$server_addr 变量传递接受请求的服务器的 IP 地址

负载均衡(反向代理)

使用 nginx 可以将 HTTP 流量负载均衡到一组服务器上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
http {
upstream backend {
zone backend 32k;
hash $request_uri consistent; # least_conn ip_hash
server backend1.example.com weight=5;
server backend2.example.com max_fails=3 fail_timeout=30s;
server backend3.example.com down;
server 192.0.0.1 backup;
}
server {
location / {
proxy_pass http://backend;
}
}
}

upstream 指令定义了一组服务器,其中 server 指令指项了每一个具体的服务器。如上,定义了一个名为 backend 的服务器组,这个组由三个服务器组成,两台主服务器,一台备份服务器,通过 proxy_pass 指令,可以将请求传递到 backend 组中。

nginx 支持四种负载均衡方法:

  1. Round Robin – 请求在服务器之间均匀分布,并考虑服务器权重。默认采用此种方法。
  2. Least Connections – 向具有最少活动连接数的服务器发送请求,再次考虑服务器权重。指令为 least_conn
  3. IP Hash – 请求发送到的服务器由客户端 IP 地址确定。在这种情况下,使用 IPv4 地址的前三个八位字节或整个 IPv6 地址来计算哈希值。该方法保证来自同一地址的请求到达同一服务器,除非它不可用。指令为 ip_hash
  4. Generic Hash - 请求发送到的服务器由用户定义的键确定,该键可以是文本字符串、变量或组合。指令为 hash 。例如,哈希密钥可能是配对的源 IP 地址和端口,或者是例子中的 URI。可选的 consistent 参数标志启用 ketama 一致哈希算法进行负载均衡。

weight 参数代表了服务器的权重,nginx 默认采用 轮询调度(Round Robin)方法根据权重在组中的服务器之间分配请求。weight 的默认值为1,值越高请求占比越多。

backup 参数表示该服务器为备份服务器,除非其他服务器均不可用,否则不会接收请求。

down 参数 表示暂时从负载均衡轮换中删除该服务器。

zone 指令表示将 upstream 的配置保存在所有工作进程(work process)共享的内存区域中。例子中的 32k 表示分配的内存大小。

max_fails 参数设置在将服务器标记为不可用之前需要发生的失败尝试次数(默认为 1 次尝试)。

fail_timeout 参数设置将服务器标记为不可用的时间(默认为 10 秒)。

压缩和解压

压缩响应通常会显着减小传输数据的大小。但是,由于压缩发生在运行时,它也会增加相当大的处理开销,从而对性能产生负面影响。nginx 支持在向客户端发送响应之前执行压缩。

1
2
3
4
5
6
7
8
9
server {
gzip on;
gzip_types text/plain application/xml;
gzip_proxied no-cache no-store private expired auth;
gzip_min_length 1000;
gunzip on;
gzip_static on;
#...
}

gzip 指令指定参数 on 即开启压缩。默认情况下,nginx 仅压缩 MIME 类型为 text/html 的响应。

gzip_types 指令指定除了 text/html 外还需要压缩的类型。

gzip_min_length 指令指定要压缩的响应的最小长度,默认为 20 字节。

gzip_proxied 指令指定来自代理服务器的请求如何进行压缩。默认情况下,nginx 不会压缩对代理请求(来自代理服务器的请求)的响应。

gunzip 指令用于在 http 客户端不支持 gzip 解压时即时解压缩。 gunzip 指令包含在 ngx_http_gunzip_module 中,默认情况下可能不包含在 nginx 的开源构建中,需要额外的构建参数 --with-http_gunzip_module

gzip_static 指令用于将文件的压缩版本而不是常规版本发送到客户端。开启后,当请求 /path/to/file 文件时,nginx 尝试查找并发送文件 /path/to/file.gz。如果文件不存在,或者客户端不支持 gzip,nginx 会发送文件的未压缩版本。gzip_static 指令包含在 ngx_http_gzip_static_module 中,默认情况下可能不包含在 nginx 的开源构建中,需要额外的构建参数 --with-http_gzip_static_module

https 支持

nginx 支持通过添加 HTTPS 服务器的相关配置将 SSL 流量转换为非加密的流量,减轻上游 Web 和应用程序服务器的负载。

要设置 HTTPS 服务器,需要在 nginx.conf 文件中将 ssl 参数包含到 server 块指令中的 listen 指令中。

1
2
3
4
5
6
7
8
9
10
11
12
13
http {
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
#...
}
}

ssl_certificate 指令指定了服务器证书的位置。

ssl_certificate_key 指令指定了私钥的位置。私钥应存储在访问受限的文件,但 nginx 主进程必须能够读取此文件。

ssl_protocolsssl_ciphers 指令可用于要求客户端在建立连接时仅使用 SSL/TLS 的指定版本和密码。

由于历史原因,nginx 默认 采用

1
2
ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;

以下是TLS/SSL协议中的主要漏洞,它们都会影响协议的旧版本(TLSv1.2及更早版本),所以目前为止,考虑客户端兼容情况,建议仅支持 TLSv1.2 及以上版本。
POODLE(Padding Oracle On Downgraded Legacy Encryption)(CVE-2014-3566)
BEAST(Browser Exploit Against SSL/TLS )(CVE-2011-3389)
CRIME (Compression Ratio Info-leak Made Easy) (CVE-2012-4929)
BREACH(Browser Reconnaissance and Exfiltration via Adaptive Compression of Hypertext) (CVE-2013-3587)
Heartbleed (CVE-2014-0160)

nginx 是通过 OpenSSL 来支持 https 的,所以 TLSv1.2 参数需要 OpenSSL 1.0.1 以上版本。

SSL 操作会消耗额外的 CPU 资源,其中最占用 CPU 的操作是 SSL 握手。

ssl_session_cache 指令配置将会话存储在工作进程之间共享的 SSL 会话缓存中。1M 字节的缓存可以包含 4000 个会话,默认缓存超时时间为5分钟。

ssl_session_timeout 指令可以修改 SSL 缓存超时时间。

nginx 常用指令说明

nginx 指令目录参见 dirindex

  • woker_processes

worker 角色的工作进程的个数,master 进程是接收并分配请求给 worker 处理。这个数值简单一点可以设置为 cpu 的核数,例如 4核 cpu 设置为 woker_processes 4; 。如果开启了 ssl 和 gzip 更应该设置成与逻辑 CPU 数量一样甚至为2倍,可以减少 I/O 操作。如果nginx服务器还有其它服务,可以考虑适当减少。

  • worker_cpu_affinity

在高并发情况下,通过设置 cpu 粘性来降低由于多 CPU 核切换造成的寄存器等现场重建带来的性能损耗。例如,4核 cpu 可以设置 worker_cpu_affinity 0001 0010 0100 1000;

  • events.worker_connections

每一个 worker 进程能并发处理(发起)的最大连接数(包含与客户端或后端被代理服务器间等所有连接数)。

  • events.use

事件模型。在 Linux 操作系统下,nginx 默认使用 epoll 事件模型,得益于此,nginx 在 Linux 操作系统下效率相当高。同时 nginx 在 OpenBSD 或 FreeBSD 操作系统上采用类似于 epoll 的高效事件模型 kqueue。在操作系统不支持这些高效模型时使用 select。

  • http.sendfile

开启高效文件传输模式,sendfile 指令指定 nginx 是否调用 sendfile 函数来输出文件,减少用户空间到内核空间的上下文切换。对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为 off,以平衡磁盘与网络 I/O 处理速度,降低系统的负载。

  • http.keepalive

长连接超时时间,单位是秒。长连接请求大量小文件的时候,可以减少重建连接的开销。如果设置时间过长,用户又多,长时间保持连接会占用大量资源。

  • http.send_timeout

用于指定响应客户端的超时时间。这个超时仅限于两个连接活动之间的时间,如果超过这个时间,客户端没有任何活动,nginx 将会关闭连接。

  • http.client_max_body_size

允许客户端请求的最大单文件字节数。

  • http.client_body_buffer_size

缓冲区代理缓冲用户端请求的最大字节数。

  • http.server.listen

http服务器的监听端口。默认80,小于1024的要以root启动。

  • http.server.server_name

服务器名,如 localhostwww.example.com,可以通过正则匹配。

  • http.upstream

设置服务器上游模块。通过调度算法来实现客户端IP到后端服务器的负载均衡。

  • http.server.location

通过 前缀字符串(路径名)和 正则表达式 两种匹配方式匹配 URI 以进行配置。

  • http.server.location.root

定义服务器的默认网站根目录位置。

  • http.server.location.index

定义目录路径下默认访问的文件名。

  • http.server.location.proxy_pass

请求转向 http.upstream 预先定义的服务器列表(即反向代理)或者 代理服务器。

  • http.server.location.proxy_set_header

在代理请求过程中重写 header 字段。

  • http.server.location.allow

访问控制,允许特定 ip 段进行访问。

  • http.server.location.deny

访问控制,禁止特定 ip 段进行访问。

  • http.server.location.autoindex

列出整个目录。默认 off

  • http.server.location.autoindex_exact_size

列出整个目录时是否显示文件的确切大小。默认为 on

  • http.server.location.autoindex_localtime

列出整个目录时是否显示本地时间。默认为 off,显示 GMT 时间。开启时显示为文件的服务器时间。