nginx

nginx 有一个主进程和多个工作进程。主进程的主要目的是读取和评估配置,并维护工作进程。工作进程负责实际的请…

nginx 有一个主进程和多个工作进程。主进程的主要目的是读取和评估配置,并维护工作进程。工作进程负责实际的请求处理。nginx 采用基于事件的模型和依赖于操作系统的机制,在工作进程之间高效地分配请求。工作进程的数量在配置文件中定义,可以针对特定配置固定,也可以根据可用的 CPU 核心数自动调整(参见 worker_processes)。

nginx 及其模块的工作方式由配置文件决定。默认情况下,配置文件的命名nginx.conf 和存放位置为 /usr/local/nginx/conf/etc/nginx/usr/local/etc/nginx

启动、停止和重新加载配置

要启动nginx,运行可执行文件。一旦nginx启动,它可以通过调用可执行文件来控制,参数为-s。使用以下语法:

nginx -s signal

其中信号可能是下列之一:

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

例如,要停止nginx进程,等待工作进程完成当前请求,可以执行以下命令:

nginx -s quit

该命令应该以启动nginx的同一用户执行。

在配置文件中所做的更改将不会被应用,直到重新加载配置的命令被发送给nginx或它被重新启动。要重新加载配置,执行:

nginx -s reload

一旦主进程接收到重新加载配置的信号,它就会检查新配置文件的语法有效性,并尝试应用其中提供的配置。如果成功,主进程启动新的工作进程,并向旧的工作进程发送消息,请求它们关闭。否则,主进程回滚更改并继续使用旧配置。旧的工作进程,收到关闭命令,停止接受新的连接,并继续服务当前的请求,直到所有的请求都被服务。之后,旧的worker进程退出。

信号也可以在Unix工具的帮助下发送到nginx进程,比如kill实用程序。在这种情况下,信号被直接发送到具有给定进程ID的进程。默认情况下,nginx主进程的进程ID被写入nginx. conf /usr/local/nginx/logs/var/run目录下的Pid。例如,如果主进程ID是1628,要发送退出信号导致nginx的安全关闭,执行:

kill -s QUIT 1628

要获取所有正在运行的nginx进程的列表,可以使用ps 实用程序,例如,如下所示:

ps -ax | grep nginx

For more information on sending signals to nginx, see Controlling nginx.

配置文件结构

Nginx由模块组成,这些模块由配置文件中指定的指令控制。指令分为简单指令和块指令。一个简单的指令由名称和参数组成,以空格分隔,并以分号(;)结束。块指令与简单指令具有相同的结构,但它以一组由大括号({})包围的附加指令结束,而不是分号。如果一个块指令可以在大括号内包含其他指令,它就被称为上下文 (examples: events, http, server, and location).

在任何上下文之外的配置文件中的指令被认为是在 main 上下文中. eventshttp 指令位于 main 上下文, server i在http中, location 位于 server.

#符号之后的其余部分被视为注释。

提供静态内容

一个重要的web服务器任务是提供文件(如图像或静态HTML页面)。

您将实现一个示例,根据请求,文件将从不同的本地目录提供:/data/www (可能包含HTML文件)和 /data/images(包含图像)。这将需要编辑配置文件,并在http块中配置server块和两个location

首先,创建 /data/www 目录,并将index.html文件放入其中,其中包含任何文本内容,并创建/data/images目录,并在其中放置一些图像。

接下来,打开配置文件。默认的配置文件已经包含了几个server块的例子,大部分都被注释掉了。现在,注释掉所有这样的块,并开始一个新的‘ server ’块:

http {
    server {
    }
}

通常,配置文件可能包括几个 server 块,根据他们的监听端口 listen 以及配置的 server names用于区分 。一旦nginx决定了哪个server处理请求,它就会根据server块中定义的location指令的参数来测试请求头中指定的URI。

添加下面的location块到server块中

location / {
    root /data/www;
}

这个location块指定与来自请求的URI进行比较的/前缀。对于匹配请求,URI将被添加到 root 指令中指定的路径中,即/data/www,以形成本地文件系统中请求文件的路径。如果有几个匹配的location块,nginx选择前缀最长的那个。上面的location块提供了长度为1的最短前缀,因此只有当所有其他location块无法提供匹配时,才会使用该块。

接下来添加第二个location

location /images/ {
    root /data;
}

它将匹配以/image/开头的请求(location /也匹配此类请求,但前缀较短)。

server 块的最终配置应该是这样的:

server {
    location / {
        root /data/www;
    }

    location /images/ {
        root /data;
    }
}

这已经是一个服务器的工作配置,它监听标准端口80,并且可以在本地机器上通过http://localhost/ 访问。为了响应uri以/images/ 开头的请求,服务器将从/data/images目录发送文件。例如,响应 http://localhost/images/example.png请求nginx将发送/data/images/example.png文件。如果这样的文件不存在,nginx将发送一个404错误的响应。uri不以/images/开头的请求将被映射到/data/www目录。例如,响应http://localhost/some/example.html请求nginx将发送/data/www/some/example.html文件。

要应用新的配置,启动nginx(如果它还没有启动),或者发送reload信号到nginx的主进程,通过执行:

nginx -s reload

如果有些东西不像预期的那样工作,你可以尝试在目录/usr/local/nginx/logs/var/log/nginx 中的access.logerror.log 文件中找出原因。

设置一个简单的代理服务器

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

我们将配置一个基本的代理服务器,它为来自本地目录的文件的图像请求提供服务,并将所有其他请求发送到代理服务器。在这个例子中,两个服务器将被定义在一个nginx实例上。

首先,通过在nginx的配置文件中添加一个 server 块来定义代理服务器,内容如下:

server {
        listen 8080;
        root /data/up1;

        location / {
        }
}

这将是一个简单的服务器,监听端口8080(以前,因为使用了标准端口80,所以没有指定listen指令),并将所有请求映射到本地文件系统上的/data/up1目录。创建这个目录,并将index.html文件放入其中。注意,root指令被放在server块中。如果具体的 location没有自己的 root,就会用 server 中的 root。

接下来,使用上一节中的服务器配置,并对其进行修改,使其成为代理服务器配置。在第一个location块中,将proxy_pass 指令与参数中指定的代理服务器的协议,名称和端口(在我们的例子中,它是http://localhost:8080)放在一起:

server {
    location / {
        proxy_pass http://localhost:8080;
    }

    location /images/ {
        root /data;
    }
}

我们将修改第二个location块,该块当前将带有/images/前缀的请求映射到/data/images目录下的文件,以使其匹配具有典型文件扩展名的图像请求。修改后的location代码块如下所示:

location ~ \.(gif|jpg|png)$ {
    root /data/images;
}

这个参数是一个正则表达式,匹配所有以 .gif.jpg.png 结尾的uri。正则表达式前面应该有~。相应的请求将被映射到/data/images目录。

当nginx选择location块来服务请求时,它首先检查指定前缀的location指令,记住最长前缀的location,然后检查正则表达式。如果正则表达式匹配,nginx会选择这个location,否则,它会选择之前记住的那个。

最终的代理服务器配置如下所示:

server {
    location / {
        proxy_pass http://localhost:8080/;
    }

    location ~ \.(gif|jpg|png)$ {
        root /data/images;
    }
}

该服务器将过滤以.gif.jpg.png结尾的请求,并将它们映射到/data/images目录(通过在root指令的参数中添加URI),并将所有其他请求传递给上述配置的代理服务器。

要应用新配置,请像前几节中描述的那样向nginx发送reload信号。

还有许多更多指令可用于进一步配置代理连接。

设置FastCGI代理

nginx可以用来将请求路由到FastCGI服务器,这些服务器运行着使用各种框架和编程语言(如PHP)构建的应用程序。

使用FastCGI服务器的最基本的nginx配置包括使用fastcgi_pass指令来代替proxy_pass指令,以及fastcgi_param指令来设置传递给FastCGI服务器的参数。假设FastCGI服务器可以在localhost:9000上访问。以上一节中的代理配置为基础,将proxy_pass指令替换为fastcgi_pass指令,并将参数更改为localhost:9000。在PHP中,SCRIPT_FILENAME参数用于确定脚本名称,QUERY_STRING参数用于传递请求参数。最终的配置将是:

server {
    location / {
        fastcgi_pass  localhost:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param QUERY_STRING    $query_string;
    }

    location ~ \.(gif|jpg|png)$ {
        root /data/images;
    }
}

这将设置一个服务器,它将通过FastCGI协议将除静态图片外的所有请求路由到运行在localhost:9000上的代理服务器。

代理ssh服务器

1. Nginx 不支持 SSH 协议的 proxy_pass

proxy_pass 是用于 HTTP/HTTPS 协议的,它不能代理像 SSH(端口22)这种基于TCP的非HTTP协议

如果你想通过 Nginx 做 TCP 层反向代理(包括 SSH),你可以用:

使用 Nginx 的 Stream 模块(支持 TCP/UDP 代理)

# 需要在 nginx.conf 的顶级配置中
stream {
    server {
        listen 443;
        proxy_pass 127.0.0.1:22;
    }
}

注意

stream 是 TCP 代理模块,必须在主配置文件 nginx.conf 的顶层定义,而不能和 HTTP 混用在一个 server 块中。

配置windows脚本

启动:

nginx.exe -p %CD%

关闭:

taskkill /IM nginx.exe /f
pause

测试配置:

nginx.exe -p %CD% -t
pause

重载配置文件:

nginx.exe -p %CD% -s reload
pause

显示进程:

tasklist /fi "imagename eq nginx.exe"
pause

Windows版本其他特性

nginx/Windows在配置中使用它运行的目录作为相对路径的前缀。在上面的例子中,前缀是C:\nginx-1.27.5\。配置文件中的路径必须使用unix风格的正斜杠指定:

access_log   logs/site.log;
root         C:/web/html;

配置https服务

要配置HTTPS服务器,必须在server块中的listening sockets上启用ssl参数,并且应该指定server certificateprivate key文件的位置:

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.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ...
}

服务器证书是一个公共实体。它被发送到每个连接到服务器的客户端。私钥是一个安全的实体,应该存储在一个访问受限的文件中,但是,它必须被nginx的主进程读取。私钥可以和证书存放在同一个文件中:

    ssl_certificate     www.example.com.cert;
    ssl_certificate_key www.example.com.cert;

在这种情况下,文件访问权限也应该受到限制。虽然证书和密钥存储在一个文件中,但只有证书被发送到客户端。

ssl_protocolsssl_ciphers指令可以用来限制连接只包含SSL/TLS的强版本和密码。默认nginx使用ssl_protocols TLSv1.2 TLSv1.3ssl_ciphers HIGH:!aNULL:!MD5因此通常不需要明确配置它们。请注意,这些指令的默认值被更改了多次。

HTTPS服务器优化

SSL操作会消耗额外的CPU资源。在多处理器系统上,应该运行几个工作进程,不少于可用CPU内核的数量。最消耗cpu的操作是SSL握手。有两种方法可以减少每个客户端的这种操作:第一种是启用持久连接,通过一个连接发送多个请求;第二种是重用SSL会话参数,以避免并行和后续连接的SSL握手。会话存储在worker之间共享的SSL会话缓存中,由ssl_session_cache指令配置。1兆字节的缓存包含大约4000个会话。默认的缓存超时时间为5分钟。可以使用ssl_session_timeout指令增加超时时间。下面是针对具有10兆字节共享会话缓存的多核系统优化的配置示例:

worker_processes auto;

http {
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;

    server {
        listen              443 ssl;
        server_name         www.example.com;
        keepalive_timeout   70;

        ssl_certificate     www.example.com.crt;
        ssl_certificate_key www.example.com.key;
        ssl_protocols       TLSv1.2 TLSv1.3;
        ssl_ciphers         HIGH:!aNULL:!MD5;
        ...

SSL证书链

有些浏览器可能会对由知名证书颁发机构(CA)签署的证书发出警告,而其他浏览器却能正常接受该证书。

虽然该证书确实是由一个受信任的机构签发的,但这个机构是通过一个中间证书来签署服务器证书的。而有些浏览器的信任证书库中并没有包含这个中间证书,因此它无法验证证书链的完整性,从而报出“不可信”的错误。

为了解决这个问题,CA 通常会提供一个中间证书链的组合文件(也叫“证书链”或“bundle”),你需要把它拼接到服务器证书后面,形成一个完整的证书文件用于配置。

注意: 拼接顺序很重要,必须是:

  1. 服务器证书(你自己网站的证书)在前
  2. 中间证书或根证书链在后
$ cat www.example.com.crt bundle.crt > www.example.com.chained.crt
ssl_certificate /path/to/fullchain.pem;  # 合并后的证书文件(服务器证书 + 中间证书)
ssl_certificate_key /path/to/private.key;

这样才能确保所有浏览器都能正确识别你的 HTTPS 证书。

生成的文件应该在ssl_certificate指令中使用:

server {
    listen              443 ssl;
    server_name         www.example.com;
    ssl_certificate     www.example.com.chained.crt;
    ssl_certificate_key www.example.com.key;
    ...
}

如果服务器证书和bundle以错误的顺序连接,nginx将无法启动并显示错误信息:

SSL_CTX_use_PrivateKey_file(" ... /www.example.com.key") failed
   (SSL: error:05800074:x509 certificate routines::key values mismatch)

因为nginx尝试将私钥与bundle的第一个证书一起使用,而不是服务器证书。

浏览器通常会缓存它们收到的中间证书,只要这些证书是由受信任的根证书颁发机构签署的。因此,一些经常使用的浏览器可能已经存有所需的中间证书,即使服务器没有发送完整的证书链,它们也不会报错或发出警告。

但这并不代表服务器配置是正确的。为了确保服务器确实发送了完整的证书链(包括中间证书),可以使用 openssl 命令行工具来检查。例如:

$ openssl s_client -connect www.godaddy.com:443
...
Certificate chain
 0 s:/C=US/ST=Arizona/L=Scottsdale/1.3.6.1.4.1.311.60.2.1.3=US
     /1.3.6.1.4.1.311.60.2.1.2=AZ/O=GoDaddy.com, Inc
     /OU=MIS Department/CN=www.GoDaddy.com
     /serialNumber=0796928-7/2.5.4.15=V1.0, Clause 5.(b)
   i:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc.
     /OU=http://certificates.godaddy.com/repository
     /CN=Go Daddy Secure Certification Authority
     /serialNumber=07969287
 1 s:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc.
     /OU=http://certificates.godaddy.com/repository
     /CN=Go Daddy Secure Certification Authority
     /serialNumber=07969287
   i:/C=US/O=The Go Daddy Group, Inc.
     /OU=Go Daddy Class 2 Certification Authority
 2 s:/C=US/O=The Go Daddy Group, Inc.
     /OU=Go Daddy Class 2 Certification Authority
   i:/L=ValiCert Validation Network/O=ValiCert, Inc.
     /OU=ValiCert Class 2 Policy Validation Authority
     /CN=http://www.valicert.com//emailAddress=info@valicert.com
...

在输出中,可以看到:

证书链(Certificate chain)分为三段:

  1. 证书 #0:
    • 这是 www.GoDaddy.com 的服务器证书(即网站的实际证书)。
    • 它的“签发者(issuer, i)”是 Go Daddy Secure Certification Authority
  2. 证书 #1:
    • 这是中间证书,由 GoDaddy 用来签署网站证书。
    • 它的签发者是 Go Daddy Class 2 Certification Authority
  3. 证书 #2:
    • 这是另一个上层的中间或根证书。
    • 它是由知名的 ValiCert, Inc. 签发的,而这个根证书已经包含在大多数浏览器的内置证书库中

🔁 证书之间的链式结构:

  • 服务器证书(#0)是由证书 #1 签发的。
  • 证书 #1 又是由证书 #2 签发的。
  • 证书 #2 是由一个受信任的根证书(ValiCert)签发的,它已经存在于浏览器的信任库中。

这就构成了一个完整的证书链,从网站证书 → 中间证书 → 根证书。


❗ 如果你没有配置证书链(bundle):

  • 那么 openssl s_client 输出里就只会显示服务器证书 #0,后面的中间证书就不会出现。
  • 这说明服务器没有发送完整证书链,某些浏览器或客户端会因此无法验证证书的有效性,从而报出“证书不可信”等错误。

像我自己的网站:

C:\Users\maluyao>openssl s_client -connect 47.99.160.141:443
CONNECTED(00000184)
Can't use SSL_get_servername
depth=0 CN = 47.99.160.141
---

verify return:1
depth=0 CN = 47.99.160.141
verify error:num=21:unable to verify the first certificate
#OpenSSL 无法验证服务器证书的完整信任链,原因通常是:服务器只发送了自己的证书,而没有发送中间证书(CA),所以客户端无法验证它的合法性。


#证书链:
---
Certificate chain
 0 s:CN = 47.99.160.141
   i:C = AT, O = ZeroSSL, CN = ZeroSSL RSA Domain Secure Site CA
---

你再看看修改之后的:

---
Certificate chain
 0 s:CN = 47.99.160.141
   i:C = AT, O = ZeroSSL, CN = ZeroSSL RSA Domain Secure Site CA
 1 s:C = AT, O = ZeroSSL, CN = ZeroSSL RSA Domain Secure Site CA
   i:C = US, ST = New Jersey, L = Jersey City, O = The USERTRUST Network, CN = USERTrust RSA Certification Authority
---

一个HTTP/HTTPS服务器

可以配置一个server来同时处理HTTP和HTTPS请求:

nginx
server {
    listen              80;
    listen              443 ssl;
    server_name         www.example.com;
    ssl_certificate     www.example.com.crt;
    ssl_certificate_key www.example.com.key;
    ...
}

Nginx 0.7.14 之前SSL 不能单独地为每个监听端口开启,像上面那样的配置是不支持的。那时,只能通过 ssl 指令为整个 server 块开启 SSL,这意味着无法同时在一个 server 中同时支持 HTTP 和 HTTPS(例如监听 80 和 443 端口)。

nginx
listen 443 ssl;

这个写法允许你只对某个监听端口开启 SSL,从而实现 HTTP 和 HTTPS 混合配置。

因此,在现代 Nginx 中,不推荐再使用 ssl 指令来开启 SSL,而是应该在 listen 中使用 ssl 参数。 并且从 Nginx 1.25.1 开始,ssl 指令已经被移除了

✅ 正确示例(现代 Nginx):

nginx
server {
    listen 80;
    listen 443 ssl;
    ...
}

❎ 错误写法(旧版本,已淘汰):

nginx
server {
    listen 443;
    ssl on;
    ...
}

基于名称的HTTPS服务器

当你配置两个或更多 HTTPS 服务器监听同一个 IP 地址 时,通常会出现一个常见问题:

一个 IP 地址上运行多个 HTTPS 网站(例如多个域名),如果客户端不支持 SNI,就无法区分这些站点,从而可能返回错误的证书。

解决方法:

  • 现代浏览器和客户端大多数都支持 SNI,所以一般情况下没问题。
  • 你需要为每个 server 配置块设置对应的 server_name 和证书(ssl_certificatessl_certificate_key)。
  • 保证 Nginx 版本较新,正确使用 listen 443 sslserver_name
server {
    listen          443 ssl;
    server_name     www.example.com;
    ssl_certificate www.example.com.crt;
    ...
}

server {
    listen          443 ssl;
    server_name     www.example.org;
    ssl_certificate www.example.org.crt;
    ...
}

在这种情况下:

  • 如果客户端支持 SNI,就能正确拿到对应域名的证书;
  • 如果客户端不支持 SNI,Nginx 会返回默认证书(一般是第一个配置的 server),可能造成证书错误。

在这种配置下,浏览器会收到默认服务器的证书(例如:www.example.com),无论实际请求的是哪个服务器名。 这其实是 SSL 协议本身的行为导致的

因为在建立 SSL 连接时,浏览器必须先和服务器完成一次 TLS 握手,而这个握手是在浏览器发送 HTTP 请求 之前 进行的。

也就是说:

nginx 在这个阶段还不知道浏览器想访问哪个域名,所以它只能返回默认的服务器证书。

解决这个问题最古老、最可靠的方法是为每个HTTPS服务器分配一个单独的IP地址:

如果你担心兼容性问题(例如还有大量老旧设备访问你的网站),最稳妥的方法是:

server {
    listen          192.168.1.1:443 ssl;
    server_name     www.example.com;
    ssl_certificate www.example.com.crt;
    ...
}

server {
    listen          192.168.1.2:443 ssl;
    server_name     www.example.org;
    ssl_certificate www.example.org.crt;
    ...
}

这样做的好处是:

  • nginx 可以通过监听的 IP 地址,提前知道要用哪个站点的证书
  • 即使浏览器不支持 SNI,也可以正确使用不同的证书;
  • 属于完全兼容老设备、老协议的做法。

缺点:

  • 每个域名需要一个独立的公网 IP 地址;
  • 对于 IPv4 地址紧张的环境,这是很不现实的方案;
  • 成本较高,配置复杂度也增加了。
方法是否需要 SNI 支持是否需要多个 IP
多 IP 绑定证书❎不需要✅ 是
单 IP + SNI✅ 需要❎ 否

包含多个名称的SSL证书

还有其他方法可以让多个HTTPS服务器共享一个IP地址。然而,它们都有各自的缺点。一种方法是在SubjectAltName证书字段中使用多个名称的证书,例如www.example.comwww.example.org。然而,SubjectAltName字段的长度是有限的。

另一种方法是使用一个带有通配符的证书名称,例如*.example.org。通配符证书保护指定域的所有子域,但只在一个级别上。此证书匹配www.example.org,但不匹配example.org和www.sub.example.org。这两种方法也可以结合使用。证书的SubjectAltName字段可能包含精确的名称和通配符名称,例如example.org*.example.org

最好将证书文件和私钥文件放在 http 级别的配置中,以便在所有服务器中继承它们的单一内存副本:

ssl_certificate     common.crt;
ssl_certificate_key common.key;

server {
    listen          443 ssl;
    server_name     www.example.com;
    ...
}

server {
    listen          443 ssl;
    server_name     www.example.org;
    ...
}

总结对比表

方法支持多个域名是否需要 SNI兼容性更新灵活性推荐场景
单域名证书 + SNI❌ 否✅ 是✅ 好(现代客户端)✅ 高多域名,现代环境
多域名证书(SAN)✅ 是❌ 否(部分旧设备不行)✅ 好❌ 低(每次需重签)多域名,无SNI支持风险
通配符证书✅ 是(仅限1级子域)❌ 否✅ 好❌ 低子域名较多
SAN + 通配符组合✅ 是❌ 否✅ 好❌ 低多种域名混合
独立 IP✅ 是❌ 否✅ 极好❌ 低(IP资源有限)要求最大兼容性

在一个IP地址上运行多个HTTPS服务器的更通用的解决方案是TLS服务器名称指示扩展 (SNI, RFC 6066),它允许浏览器在SSL握手期间传递请求的服务器名称,因此服务器将知道它应该为连接使用哪个证书。SNI目前被大多数现代浏览器支持,是TLSv1.3中必须实现的扩展,但一些老客户端或特殊客户端可能不会使用。

SNI只能传递域名,但如果请求包含字面的IP地址,有些浏览器可能会将服务器的IP地址作为名称传递。人们不应该依赖于此。

为了在nginx中使用SNI,必须在构建nginx二进制文件的OpenSSL库和运行时动态链接nginx的库中都支持SNI。如果使用配置选项“——enable-tlsext”构建,OpenSSL从0.9.8f版本开始支持SNI。因为OpenSSL 0.9.8j这个选项是默认启用的。如果nginx构建时支持SNI,那么当使用“-V”开关运行时,nginx会显示如下信息:

$ nginx -V
...
TLS SNI support enabled
...

但是,如果支持SNI的nginx动态链接到不支持SNI的OpenSSL库,nginx会显示警告:

nginx was built with SNI support, however, now it is linked
dynamically to an OpenSSL library which has no tlsext support,
therefore SNI is not available

image-20250520223209079

使用nginx作为HTTP负载均衡器

介绍

跨多个应用实例的负载均衡是优化资源利用、最大化吞吐量、减少延迟和确保容错配置的常用技术。

使用nginx作为一个非常高效的HTTP负载均衡器,可以将流量分配到多个应用服务器,并通过nginx提高web应用的性能、可伸缩性和可靠性。

使用负载均衡方法

nginx支持以下负载均衡机制:

  • Round-robin—对应用服务器的请求以轮询方式分发。
  • least-connected — 下一个请求被分配给活动连接数最少的服务器,
  • ip-hash — 散列函数用于确定下一个请求应该选择哪个服务器(基于客户端的IP地址)。

默认负载均衡配置

使用nginx进行负载均衡的最简单配置如下所示:

http {
    upstream myapp1 {
        server srv1.example.com;
        server srv2.example.com;
        server srv3.example.com;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://myapp1;
        }
    }
}

在上面的例子中,同一个应用程序有3个实例运行在srv1-srv3上。当没有特别配置负载均衡方法时,默认使用轮询方式。所有请求都被proxy_pass到服务器组myapp1,并且nginx应用HTTP负载均衡来分发请求。

nginx中的反向代理实现包括HTTP、HTTPS、FastCGI、uwsgi、SCGI、memcached和gRPC的负载均衡。

要为HTTPS而不是HTTP配置负载均衡,只需使用“HTTPS”作为协议。

当为FastCGI、uwsgi、SCGI、memcached或gRPC设置负载均衡时,请分别使用fastcgi_pass, uwsgi_pass, scgi_pass, memcached_pass, 和grpc_pass 指令.

最小连接负载均衡

另一种负载均衡策略是“最少连接数”(least-connected)。当某些请求需要更长时间才能完成时,使用最少连接数策略可以让各个应用实例的负载更加公平地分配。

在最少连接数的负载均衡模式下,Nginx 会尽量避免向繁忙的应用服务器发送过多请求,而是将新请求分配给当前连接数较少、相对空闲的服务器。

当使用least_conn指令作为服务器组配置的一部分时,nginx中的最少连接负载均衡被激活:

upstream myapp1 {
    least_conn;
    server srv1.example.com;
    server srv2.example.com;
    server srv3.example.com;
}

会话持久策略

请注意,使用轮询或最少连接负载均衡,每个后续客户端的请求都可能被分发到不同的服务器。不能保证同一个客户端总是指向同一个服务器。

如果需要将客户端绑定到特定的应用服务器——换句话说,使客户端的会话具有“粘性”或“持久性”,即始终尝试选择特定的服务器——则可以使用ip-hash负载均衡机制。

使用IP -hash,客户端的IP地址被用作散列键,以确定服务器组中应该为客户端的请求选择哪台服务器。此方法确保来自同一客户端的请求将始终定向到同一服务器,除非此服务器不可用。

要配置ip-hash负载均衡,只需将ip_hash指令添加到服务器(上游)组配置:

upstream myapp1 {
    ip_hash;
    server srv1.example.com;
    server srv2.example.com;
    server srv3.example.com;
}

假设你有一个电商网站,用户登录后信息保存在服务器 A 上。如果每次请求都被分配到不同的服务器(比如 A、B、C 之间来回切换),那么服务器 B 和 C 可能就不知道用户已经登录了。

使用 ip-hash 后,只要用户是从同一个 IP 发起请求,就会一直被分配到服务器 A,这样就能保证用户的登录状态不会丢失。

加权负载平衡

通过使用服务器权重也有可能进一步影响nginx负载均衡算法。

在上面的例子中,服务器权重没有配置,这意味着所有指定的服务器都被视为同等合格的特定负载均衡方法。

特别是轮询,它还意味着请求在服务器上或多或少的平均分布——前提是有足够的请求,并且请求以统一的方式处理并足够快地完成。

当为服务器指定weight参数时,权重将作为负载平衡决策的一部分进行计算。

    upstream myapp1 {
        server srv1.example.com weight=3;
        server srv2.example.com;
        server srv3.example.com;
    }

在此配置下,每5个新请求将按如下方式分发到应用程序实例:3个请求将定向到srv1,一个请求将定向到srv2,另一个请求将定向到srv3。

最新版本的 Nginx 中,也可以在 最少连接数(least-connected)IP 哈希(ip-hash) 这些负载均衡方式中使用 权重(weights)

nginx如何处理请求

基于名称的虚拟服务器

Nginx首先决定哪个服务器应该处理请求。让我们从一个简单的配置开始,所有三个虚拟服务器都监听端口*:80:

server {
    listen      80;
    server_name example.org www.example.org;
    ...
}

server {
    listen      80;
    server_name example.net www.example.net;
    ...
}

server {
    listen      80;
    server_name example.com www.example.com;
    ...
}

在这个配置中,nginx只测试请求头字段“Host”,以确定请求应该路由到哪台服务器。如果它的值不匹配任何服务器名称,或者请求根本不包含此header字段,那么nginx将把请求路由到此端口的默认服务器。在上面的配置中,第一个是默认服务器——这是nginx的标准默认行为。它也可以明确设置哪个服务器应该是默认的,在listen指令中使用default_server参数:

server {
    listen      80 default_server;
    server_name example.net www.example.net;
    ...
}

default_server参数从0.8.21版本开始可用。在早期的版本中,应该使用default参数。

注意,默认服务器是监听端口的属性,而不是服务器名。稍后会详细介绍。

如何防止处理未定义的服务器名称的请求

如果不允许不带Host头字段的请求,可以定义一个删除请求的服务器:

server {
    listen      80;
    server_name "";
    return      444;
}

在这里,服务器名被设置为一个空字符串,以匹配没有“Host”头字段的请求,并返回一个特殊的nginx的非标准代码444来关闭连接。

从0.8.48版本开始,这是服务器名称的默认设置,因此可以省略server_name ""。在早期的版本中,主机名被用作默认的服务器名。

混合基于名称和基于ip的虚拟服务器

让我们看一个更复杂的配置,一些虚拟服务器监听不同的地址:

server {
    listen      192.168.1.1:80;
    server_name example.org www.example.org;
    ...
}

server {
    listen      192.168.1.1:80;
    server_name example.net www.example.net;
    ...
}

server {
    listen      192.168.1.2:80;
    server_name example.com www.example.com;
    ...
}

在这个配置中,nginx首先根据server块中的listen指令测试请求的IP地址和端口。然后,它根据与IP地址和端口匹配的server块中的server_name条目测试请求的“Host”头字段。如果没有找到服务器名,请求将由默认服务器处理。例如,从192.168.1.1:80端口接收到的www.example.com请求将被192.168.1.1:80端口的默认服务器处理,即由第一个服务器处理,因为没有为这个端口定义www.example.com

如前所述,默认服务器是监听端口的一个属性,不同的端口可以定义不同的默认服务器:

server {
    listen      192.168.1.1:80;
    server_name example.org www.example.org;
    ...
}

server {
    listen      192.168.1.1:80 default_server;
    server_name example.net www.example.net;
    ...
}

server {
    listen      192.168.1.2:80 default_server;
    server_name example.com www.example.com;
    ...
}

一个简单的PHP站点配置

现在让我们看看nginx如何为一个典型的简单PHP网站选择一个处理请求的location

server {
    listen      80;
    server_name example.org www.example.org;
    root        /data/www;

    location / {
        index   index.html index.php;
    }

    location ~* \.(gif|jpg|png)$ {
        expires 30d;
    }

    location ~ \.php$ {
        fastcgi_pass  localhost:9000;
        fastcgi_param SCRIPT_FILENAME
                      $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}

Nginx首先搜索由字符串字面量给出的最具体的前缀位置,而不管列出的顺序如何。在上面的配置中,唯一的前缀位置是“/”,因为它匹配任何请求,所以它将被用作最后的手段。然后nginx按照配置文件中列出的顺序检查正则表达式给出的位置。第一个匹配的表达式停止搜索,nginx将使用这个位置。如果没有正则表达式匹配请求,则nginx使用之前找到的最具体的前缀位置。

注意,所有类型的位置都只测试请求行中没有参数的URI部分。这样做是因为查询字符串中的参数可能有几种提供方式,例如:

/index.php?user=john&page=1
/index.php?page=1&user=john

此外,任何人都可以请求查询字符串中的任何内容:

/index.php?page=1&something+else&user=john

现在让我们看看在上面的配置中如何处理请求:

  • 一个请求“/logo.gif”首先通过前缀位置“/”匹配,然后通过正则表达式“\.(gif|jpg|png)$”匹配,因此,它由后者处理。使用指令“root /data/www”,请求被映射到文件/data/www/logo.gif,文件被发送到客户端。

  • 一个请求“/index.php”也会先匹配前缀位置“/”,然后匹配正则表达式“\.(php)$”。因此,它由后一个位置处理,并且请求被传递给一个在localhost:9000上监听的FastCGI服务器。fastcgi_param指令将FastCGI参数SCRIPT_FILENAME设置为“/data/www/index.php”,然后FastCGI服务器执行文件。变量$document_root等于root指令的值,变量$fastcgi_script_name等于请求的URI,即/index.php

  • 请求“/about.html”只与前缀位置“/”匹配,因此,它是在这个位置处理的。使用指令“root /data/www”,请求被映射到文件/data/www/about.html,然后文件被发送到客户端。

  • 处理“/”请求更加复杂。它只与前缀位置“/”匹配,因此,它由该位置处理。然后index指令根据其参数和“root /data/www”指令测试索引文件的存在性。如果文件/data/www/index.html不存在,而文件/data/www/index.php存在,那么指令会进行一次内部重定向/index.php, nginx会像客户端发送的请求一样再次搜索这些位置。正如我们之前看到的,重定向的请求最终会被FastCGI服务器处理。

Server names

服务器名称使用server_name指令定义,并确定哪个Server块用于给定的请求。请参见“nginx如何处理请求”。它们可以使用精确名称、通配符名称或正则表达式定义:

server {
    listen       80;
    server_name  example.org  www.example.org;
    ...
}

server {
    listen       80;
    server_name  *.example.org;
    ...
}

server {
    listen       80;
    server_name  mail.*;
    ...
}

server {
    listen       80;
    server_name  ~^(?<user>.+)\.example\.net$;
    ...
}

当通过名称搜索虚拟服务器时,如果名称匹配指定的多个变体,例如通配符名称和正则表达式同时匹配,则将按以下优先顺序选择第一个匹配的变体:

  1. 精准名称
  2. 以星号开头的最长通配符名, e.g. “*.example.org
  3. 以星号结尾的最长通配符名, e.g. “mail.*
  4. 第一个匹配的正则表达式(按照在配置文件中出现的顺序)

通配符的名称

通配符名称只能在名称的开头或结尾,并且只能在句点边界上包含星号。名称 “www.*.example.org” 与 “w*.example.org” 是不同的。不过,这些名称可以使用正则表达式指定,例如, “~^www\..+\.example\.org$” and “~^w.*\.example\.org$”.。星号可以匹配多个名称部分。名称 “*.example.org” 不仅匹配www.example.org 还匹配www.sub.example.org

A special wildcard name in the form “.example.org” can be used to match both the exact name “example.org” and the wildcard name “*.example.org”.

.example.org ”这种特殊的通配符既可以匹配“ example.org ”,也可以匹配“ *.example.org ’”。

正则表达式名称

nginx使用的正则表达式与Perl编程语言(Perl programming language, PCRE)使用的正则表达式是兼容的。要使用正则表达式,服务器名必须以波浪号开头:

server_name  ~^www\d+\.example\.net$;

否则,它将被视为精确名称,或者如果表达式包含星号,则将其视为通配符名称(很可能是无效名称)。不要忘记设置“^” and “$” 在头尾.。它们在语法上不是必需的,但在逻辑上是必需的。还要注意,域名的点应该用反斜杠转义。包含字符“{”和“} ”的正则表达式应该用引号(“”)括起来:

server_name  "~^(?<name>\w\d{1,3}+)\.example\.net$";

否则nginx将启动失败并显示错误信息:

directive "server_name" is not terminated by ";" in ...

命名正则表达式捕获后可以作为变量使用:

server {
    server_name   ~^(www\.)?(?<domain>.+)$;

    location / {
        root   /sites/$domain;
    }
}

PCRE库支持使用以下语法进行命名捕获:

?<*name*>Perl 5.10 compatible syntax, supported since PCRE-7.0
?'*name*'Perl 5.10 compatible syntax, supported since PCRE-7.0
?P<*name*>Python compatible syntax, supported since PCRE-4.0

如果nginx启动失败并显示错误信息:

pcre_compile() failed: unrecognized character after (?< in ...

这意味着PCRE库是旧的,语法 ?P<*name*>应该被尝试代替。

捕捉也可以以数字形式使用:

server {
    server_name   ~^(www\.)?(.+)$;

    location / {
        root   /sites/$2;
    }
}

但是,这种用法应该限制在简单的情况下(如上所示),因为数字引用很容易被覆盖。

各种各样的名字

有一些服务器名需要特殊处理。

如果需要在server块中处理没有“Host”头字段的请求(这不是默认值),则应该指定一个空名称:

server {
    listen       80;
    server_name  example.org  www.example.org  "";
    ...
}

如果在server块中没有定义server_name,则nginx使用空名称作为服务器名称。

如果将服务器名称定义为 $hostname (从 Nginx 0.9.4 版本开始支持),那么 Nginx 将使用这台机器本身的主机名(hostname) 来作为服务器名。

如果有人使用IP地址而不是服务器名称发起请求,“Host”请求头字段将包含IP地址,可以使用IP地址作为服务器名称来处理请求:

server {
    listen       80;
    server_name  example.org
                 www.example.org
                 ""
                 192.168.1.1
                 ;
    ...
}

在一些 “通配服务器” (catch-all server)的 Nginx 配置示例中,你会看到一个看起来很奇怪的名字 “_”。它通常被用作 server_name 的值。

server {
    listen       80  default_server;
    server_name  _;
    return       444;
}

这个名称(指 _)本身并没有什么特殊之处 ,它只是无数个无效域名中的一个 ,这些无效域名永远不会与任何真实的域名发生重叠。像 “--” 和 “!@#” 这样的其他无效名称也同样可以使用。

Nginx 0.6.25 及更早版本中 ,支持一个特殊的名称 *,它曾被误认为是一个“通配符”或“捕获所有”的服务器名。

但实际上:

这个特殊名称 * 现在已经被 弃用(deprecated) ,你应该使用新的 server_name_in_redirect 指令来替代其功能。

你可以定义多个监听不同端口(如 *:80*:8080)的服务器,并分别指定:

  • 一个服务器是 *:8080 端口的默认服务器
  • 另一个服务器是 *:80 端口的默认服务器
server {
    listen       80;
    listen       8080  default_server;
    server_name  example.net;
    ...
}

server {
    listen       80  default_server;
    listen       8080;
    server_name  example.org;
    ...
}

国际化域名

国际化域名(IDN)在 Nginx 的 server_name 指令中应该使用 ASCII 格式(即 Punycode 编码形式)来配置。

server {
    listen       80;
    server_name  xn--e1afmkfd.xn--80akhbyknj4f;  # пример.испытание
    ...
}
什么是 IDN(国际化域名)?

IDN(Internationalized Domain Name )是指包含非 ASCII 字符(如中文、日文、俄文、阿拉伯文等)的域名,例如:

пример.рф(俄语)

例子.中国(中文)

由于 DNS 系统最初只支持 ASCII 字符(a-z、0-9 和连字符 -),为了兼容这些非 ASCII 域名,引入了 Punycode 编码


Punycode 是什么?

Punycode 是一种将 Unicode 字符转换为纯 ASCII 字符串的编码方式。它会在域名前加上一个特殊的前缀 xn--,以标识这是一个经过编码的国际化域名。

例如:

国际化域名Punycode 编码
пример.рфxn—e1afmkfd.xn—p1ai
例子.中国xn—fsqu73b4cy4c.xn—fiqz9s
使用在线工具:

虚拟主机的选择

首先,连接是基于 默认服务器上下文(default server context) 建立的。然后,在请求处理的不同阶段,可以确定具体的服务器名称,并据此选择合适的服务器配置来处理请求。这些阶段包括:

  1. 在 SSL 握手阶段提前根据 [SNI](https://nginx.org/en/docs/http/configuring_https_servers.html #sni) 确定服务器名称
  2. 在处理请求行(request line)时
  3. 在处理 Host 请求头字段时
  4. 如果在处理请求行或 Host 头之后仍无法确定服务器名称,Nginx 将使用空名称作为服务器名

在这些阶段中,可以应用不同的服务器配置。因此,某些指令应该谨慎指定:

  • ssl_protocols指令的情况下,在根据SNI请求的名称应用服务器配置之前,协议列表是由OpenSSL库设置的,因此,协议应该仅为默认服务器指定;
  • 在读取请求行之前涉及client_header_buffer_sizemerge_slash指令,因此,这些指令使用默认的服务器配置或SNI选择的服务器配置;
  • 在涉及处理请求头字段的ignore_invalid_headers, large_client_header_buffersunderscores_in_headers指令的情况下,另外,它还取决于服务器配置是根据请求行还是Host头字段更新的;
  • 错误响应将通过当前完成请求的服务器中的error_page指令处理。

中文翻译与详细解析:

在每个请求处理阶段,Nginx 可能会应用不同的服务器配置。因此,某些指令应谨慎使用:

1. ssl_protocols 指令
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
  • 问题原因:

    • 这个指令用于设置 SSL/TLS 协议版本。
    • 它是由 OpenSSL 库在 SSL 握手阶段就使用的。
    • 而此时服务器名还未通过 SNI 确定,无法选择具体的虚拟主机配置。
  • 建议做法:

    只能在 默认服务器(default_server) 上配置 ssl_protocols,否则配置可能不会生效或产生意外行为。


2. client_header_buffer_sizemerge_slashes 指令
client_header_buffer_size 1k;

merge_slashes on;
  • 问题原因:

    • 这些指令在读取请求行(request line)之前就会被用到。
    • 此时还未根据请求中的 Host 或 SNI 来切换虚拟主机配置。
  • 生效规则:

    使用的是:

    • 默认服务器配置
    • 或者通过 SNI 已经匹配到的服务器配置(如果 SSL 握手已完成)

3. ignore_invalid_headerslarge_client_header_buffersunderscores_in_headers 指令
ignore_invalid_headers on;

large_client_header_buffers 4 8k;

underscores_in_headers on;
  • 问题原因:

    • 这些指令用于控制 HTTP 请求头的解析。
    • 它们是在处理请求头字段时使用的。
    • 此时是否已经更新为正确的虚拟主机配置,取决于是否已通过请求行或 Host 头成功匹配了目标服务器。
  • 影响因素:

    如果在处理请求头时尚未完成虚拟主机的选择,则仍然使用默认服务器配置。


4. error_page 指令
error_page 404 /custom_404.html;
  • 作用说明:

    • 当发生错误(如 495、496、497 等非标准 HTTPS 错误)时,由当前正在处理该请求的虚拟主机来决定如何响应。
  • 关键点:

    error_page 的配置是基于当前选中的服务器块生效的。

优化

确切的名称、以星号开头的通配符名称和以星号结尾的通配符名称存储在绑定到侦听端口的三个散列表中。散列表的大小在配置阶段进行优化,这样可以找到一个名称,且CPU高速缓存缺失次数最少。建立散列表的细节在一个单独的文档中提供。

首先查找精确的names散列表。如果没有找到名称,则搜索以星号开头的通配符名称的散列表。如果没有找到该名称,则搜索以星号结尾的通配符名称的散列表。

查找通配符名称散列表比查找精确名称散列表慢,因为名称是根据域部分进行查找的。注意,特殊的通配符形式“ .example.org ”存储在通配符名称散列表中,而不是精确名称散列表中。

正则表达式是按顺序测试的,因此是最慢的方法,并且不可扩展。

出于这些原因,最好尽可能使用精确的名称。例如,如果最常被请求的服务器名称是example.orgwww.example.org,那么显式地定义它们会更高效:

server {
    listen       80;
    server_name  example.org  www.example.org  *.example.org;
    ...
}

或者使用简单的形式:

server {
    listen       80;
    server_name  .example.org;
    ...
}

如果定义了大量的服务器名,或者定义了异常长的服务器名,可能需要在http级别调整server_names_hash_max_sizeserver_names_hash_bucket_size指令。

Nginx 在处理 server_name 时,内部使用一个 哈希表(hash table) 来快速查找和匹配请求中的 Host 头与配置中的 server_name

1、server_names_hash_max_size

  • 作用:
    • 设置用于存储 server_name 的哈希表的最大容量(即桶的数量)。

2、server_names_hash_bucket_size

  • 作用:
    • 设置每个哈希桶的大小。它必须是 CPU 缓存行大小的倍数(通常是 32 或 64 字节)。

server_names_hash_bucket_size指令的默认值可能等于32,或64,或其他值,取决于CPU缓存行大小。如果默认值是32,并且服务器名被定义为“ too.long.server.name.example.org ”,那么nginx将启动失败并显示错误消息:

could not build the server_names_hash,
you should increase server_names_hash_bucket_size: 32

在这种情况下,指令值应该增加到2的下一个幂:

http {
    server_names_hash_bucket_size  64;
    ...

如果定义了大量服务器名,则会出现另一条错误消息:

could not build the server_names_hash,
you should increase either server_names_hash_max_size: 512
or server_names_hash_bucket_size: 32

在这种情况下,首先尝试将server_names_hash_max_size设置为接近服务器名称数量的数字。只有在这样做没有帮助的情况下,或者nginx的启动时间长得令人无法接受时,才尝试增加server_names_hash_bucket_size

如果一台服务器只有一个监听端口,那么nginx根本不会测试服务器名(也不会为监听端口构建散列表)。然而,有一个例外。如果服务器名是一个包含捕获的正则表达式,那么nginx必须执行该表达式来获取捕获。

Nginx 是如何处理 TCP/UDP 会话的?

客户端发起的一个 TCP 或 UDP 会话,在 Nginx 中是按照一系列连续的阶段(phases)逐步处理的:


🧩 阶段一:Post-accept(连接建立后)

这是接受客户端连接之后的第一个阶段。

  • 作用:
    • 可以在此阶段修改客户端的真实 IP 地址。
  • 调用模块:
    • ngx_stream_realip_module:用于识别真实客户端 IP(例如当使用代理时)。

🧩 阶段二:Pre-access(访问前检查)

在正式处理数据之前进行初步访问控制。

  • 作用:
    • 进行连接数限制、设置变量等操作。
  • 调用模块:
    • ngx_stream_limit_conn_module:限制并发连接数。
    • ngx_stream_set_module:设置变量。

🧩 阶段三:Access(访问控制)

在实际处理数据之前进行客户端访问权限控制。

  • 作用:
    • 控制哪些客户端可以继续通信。
  • 调用模块:
    • ngx_stream_access_module:基于 IP 的黑白名单访问控制。
  • njs 支持:
    • 使用 js_access 指令实现 JavaScript 编写的访问控制逻辑。

🧩 阶段四:SSL(TLS/SSL 终止)

如果启用了 SSL/TLS 加密,这个阶段负责加密通信的终止。

  • 作用:
    • 处理 TLS 握手,解密流量。
  • 调用模块:
    • ngx_stream_ssl_module:提供 SSL/TLS 支持。

🧩 阶段五:Preread(预读取)

在这个阶段,Nginx 会从客户端读取少量初始数据到“预读缓冲区”。

  • 作用:
    • 允许某些模块分析数据,比如判断是否为 HTTPS 请求、SNI 名称等。
  • 调用模块:
    • ngx_stream_ssl_preread_module:用于获取 SNI、ALPN 等信息,而无需解密。
  • njs 支持:
    • 使用 js_preread 指令对数据进行预处理。

🧩 阶段六:Content(内容处理)【必填阶段】

这是最关键的一个阶段,用来处理实际的数据流。

  • 作用:
    • 将数据转发给后端服务器(proxy)、返回固定值等。
  • 常见行为:
    • 数据被代理到 upstream 服务器
    • 返回特定响应(如拒绝服务)
  • njs 支持:
    • 使用 js_filter 指令进行数据过滤和修改。

🧩 阶段七:Log(日志记录)

这是最后一个阶段,用于记录本次会话的处理结果。

  • 作用:
    • 记录日志信息,如连接时间、传输数据量、状态码等。
  • 调用模块:
    • ngx_stream_log_module:记录访问日志。

📋 总结表格

阶段主要用途常用模块 / 功能
Post-accept修改客户端真实 IPreal_ip
Pre-access初步访问控制 / 设置变量limit_conn,set
Access客户端访问权限控制access,js_access
SSLTLS/SSL 握手与加密处理ssl
Preread预读取部分数据供分析ssl_preread,js_preread
Content核心数据处理(代理或返回响应)proxy_pass,return,js_filter
Log日志记录log

💡 实际应用举例(stream 配置示例)

nginx
stream {

    upstream backend {

        server 192.168.0.10:12345;

    }

    server {

        listen 1234;
        # 设置客户端真实 IP
        set_real_ip_from 192.168.0.0/24;
        real_ip_header $proxy_protocol_addr;
        # 限制最大连接数
        limit_conn addr 10
        # 白名单控制
        deny 192.168.0.100;
        allow all;
        # SSL 配置
        ssl on;
        ssl_certificate /etc/nginx/cert.pem;
        ssl_certificate_key /etc/nginx/key.pem;
        # 预读取用于判断协议
        ssl_preread on;
        # 实际处理转发
        proxy_pass backend;
        # 日志记录
        access_log /var/log/nginx/stream.log;

    }

}

WebSocket proxying

要将客户端和服务器之间的连接从HTTP/1.1转换为WebSocket,需要使用HTTP/1.1中可用的协议开关机制。

然而,有一个微妙之处:由于“Upgrade”是一个逐跳标头,它不会从客户端传递到代理服务器。使用正向代理,客户端可以使用CONNECT方法来规避这个问题。但反向代理不支持这种方式,因为客户端不知道代理服务器,需要在代理服务器上进行特殊处理。

从版本1.3.13开始,nginx实现了特殊的操作模式,如果代理服务器返回代码101(切换协议),并且客户端通过请求中的“Upgrade”头请求协议切换,则允许在客户端和代理服务器之间建立隧道。

如上所述,包括“Upgrade”和“Connection”在内的逐跳首部不会从客户端传递到代理服务器,因此为了让代理服务器知道客户端想要将协议切换到WebSocket的意图,必须显式地传递这些首部:

location /chat/ {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

下面是一个更复杂的例子,在发送到代理服务器的请求中,“Connection”头字段的值取决于客户端请求头中是否存在“Upgrade”字段:

http {
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    server {
        ...

        location /chat/ {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
        }
    }

默认情况下,如果代理服务器在60秒内没有传输任何数据,连接将关闭。这个超时时间可以用proxy_read_timeout指令增加。或者,可以配置代理服务器定期发送WebSocket ping帧,以重置超时时间并检查连接是否仍然活着。

转换重写规则

nginx重定向有两种方式

  • return
  • rewrite

跳转到主站

那些在共享主机环境中生活时,习惯于只使用 Apache 的 .htaccess 文件来配置一切功能 的人,在迁移到 Nginx 时,通常会把以下这些规则“翻译”成对应的 Nginx 配置。

RewriteCond  %{HTTP_HOST}  example.org
RewriteRule  (.*)          http://www.example.org$1

或者这些

server {
    listen       80;
    server_name  www.example.org  example.org;
    if ($http_host = example.org) {
        rewrite  (.*)  http://www.example.org$1;
    }
    ...
}

这是一种错误的、繁琐的、无效的方法。正确的方法是为example.org定义一个单独的服务器:

server {
    listen       80;
    server_name  example.org;
    return       301 http://www.example.org$request_uri;
}

server {
    listen       80;
    server_name  www.example.org;
    ...
}

在0.9.1之前的版本中,重定向可以通过以下方式进行:

    rewrite      ^ http://www.example.org$request_uri?;

Another example. Instead of the “upside-down” logic “all that is not example.com and is not www.example.com”:

另一个例子。而不是“所有不是example.com也不是www.example.com”的“颠倒”逻辑:

RewriteCond  %{HTTP_HOST}  !example.com
RewriteCond  %{HTTP_HOST}  !www.example.com
RewriteRule  (.*)          http://www.example.com$1

你可以简单地定义example.comwww.example.com和“everything else”:

server {
    listen       80;
    server_name  example.com www.example.com;
    ...
}

server {
    listen       80 default_server;
    server_name  _;
    return       301 http://example.com$request_uri;
}

在0.9.1之前的版本中,重定向可以通过以下方式进行:

    rewrite      ^ http://example.com$request_uri?;

转换Mongrel规则

典型的杂种规则:

DocumentRoot /var/www/myapp.com/current/public

RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*$ %{DOCUMENT_ROOT}/system/maintenance.html [L]

RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^(.*)$ $1 [QSA,L]

RewriteCond %{REQUEST_FILENAME}/index.html -f
RewriteRule ^(.*)$ $1/index.html [QSA,L]

RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^(.*)$ $1.html [QSA,L]

RewriteRule ^/(.*)$ balancer://mongrel_cluster%{REQUEST_URI} [P,QSA,L]

应该转换为

location / {
    root       /var/www/myapp.com/current/public;

    try_files  /system/maintenance.html
               $uri  $uri/index.html $uri.html
               @mongrel;
}

location @mongrel {
    proxy_pass  http://mongrel;
}

https://nginx.org/en/docs/http/ngx_http_rewrite_module.html

https://nginx.org/en/docs/http/ngx_http_map_module.html

ngx_http_map_module

ngx_http_map_module 是 Nginx 的一个模块,它允许你创建变量 ,这些变量的值会根据其他变量的值 而变化。这个功能非常适合用于根据请求的不同条件设置不同的行为。

这个模块的核心就是 map 指令。它定义了一个从源变量结果变量的映射关系。当 map 定义的变量被使用时,Nginx 才会去计算它的值,所以即使你定义了大量的 map 变量,只要它们不被实际用到,就不会产生额外的性能开销。

nginx
map $http_host $name {
    hostnames;

    default       0;

    example.com   1;
    *.example.com 1;
    example.org   2;
    *.example.org 2;
    .example.net  3;
    wap.*         4;
}

map $http_user_agent $mobile {
    default       0;
    "~Opera Mini" 1;
}

map 块内部定义了具体的映射规则:

  • 源值:可以是字符串或者正则表达式
    • 字符串匹配:不区分大小写。
    • 正则表达式匹配:
      • ~ 开头表示区分大小写的匹配。
      • ~* 开头表示不区分大小写的匹配(Nginx 1.0.4 版本后支持)。
      • 正则表达式中可以包含捕获组,这些捕获组可以在后续的指令中与结果变量一起使用。
    • 如果源值与下面提到的特殊参数(如 defaulthostnames 等)名称相同,需要用 \ 符号进行转义。
  • 结果值:可以是文本、其他变量(Nginx 0.9.0 版本后支持)或它们的组合(Nginx 1.11.0 版本后支持)。

特殊参数

map 块中还支持一些特殊的参数来控制映射行为:

  • default value:当源值没有匹配到任何已定义的规则时,Nginx 会将结果变量设置为 default 后面的 value。如果 default 没有指定,默认的结果值将是一个空字符串。
  • hostnames:这个参数表明源值可以是带有前缀或后缀掩码的主机名。
    • 例如:*.example.com 1;example.* 1;
    • example.com 1;*.example.com 1; 可以合并写成 .example.com 1;
    • hostnames 参数必须放在映射列表之前。
  • include file:可以包含一个外部文件,文件中定义了更多的映射规则。可以包含多个文件。
  • volatile:表示这个变量是不可缓存的(Nginx 1.11.7 版本后支持)。

匹配优先级

当源值可以匹配到多个规则时(例如同时匹配到一个掩码和一个正则表达式),Nginx 会按照以下优先级选择第一个匹配到的规则:

  1. 不带掩码的字符串值
  2. 带前缀掩码的最长字符串值,例如 *.example.com
  3. 带后缀掩码的最长字符串值,例如 mail.*
  4. 第一个匹配的正则表达式(按照在配置文件中出现的顺序)。
  5. default
nginx
map $http_host $name {
    hostnames; # 启用主机名匹配

    default         0; # 默认值为 0

    example.com     1; # 如果主机是 example.com,则 $name 为 1
    *.example.com   1; # 如果主机是 *.example.com,则 $name 为 1
    example.org     2; # 如果主机是 example.org,则 $name 为 2
    *.example.org   2; # 如果主机是 *.example.org,则 $name 为 2
    .example.net    3; # 这是 .example.net 的简写形式,包括 example.net 和所有子域名
    wap.* 4; # 如果主机是 wap.开头,则 $name 为 4
}

在这个例子中:

  • $http_host 是源变量,表示 HTTP 请求中的 Host 头。
  • $name 是新创建的结果变量。
  • 如果请求的 Hostwww.example.com,那么 $name 的值就是 1
  • 如果请求的 Hosttest.example.org,那么 $name 的值就是 2
  • 如果请求的 Hostblog.example.net,那么 $name 的值就是 3
  • 如果请求的 Hostfoo.com,因为它没有匹配到任何规则,那么 $name 的值就是 0(因为设置了 default 0)。
map $http_user_agent $mobile {
    default         0;
    "~Opera Mini"   1; # 如果用户代理字符串包含 "Opera Mini" (区分大小写),则 $mobile 为 1
}

散列表配置

为了优化 map 变量的查找性能,Nginx 提供了两个指令来配置散列表:

  • map_hash_bucket_size size;:设置 map 变量散列表的桶大小。默认值取决于处理器的缓存行大小。
  • map_hash_max_size size;:设置 map 变量散列表的最大大小。默认是 2048。

变量

https://nginx.org/en/docs/varindex.html

ngx_http_index_module 模块详解

ngx_http_index_module 模块主要用来处理那些以斜杠(/)结尾的请求。比如,当用户访问一个目录而不是一个具体文件时(例如访问 http://example.com/images/),这个模块就会介入,尝试寻找并提供一个默认的索引文件。

需要注意的是,这类请求也可以由 ngx_http_autoindex_module(自动生成目录列表)和 ngx_http_random_index_module(随机选择索引文件)模块来处理。


示例配置

一个常见的配置示例如下:

nginx
location / {
    index index.$geo.html index.html;
}

在这个例子中,当请求 / 时,Nginx 会:

  1. 首先尝试查找 index.$geo.html 文件(这里的 $geo 是一个变量,表示 Nginx 可以根据地理位置等信息动态决定文件名)。
  2. 如果 index.$geo.html 不存在,Nginx 会接着尝试查找 index.html
  3. 如果找到了其中一个文件,Nginx 就会把该文件作为响应返回给客户端。

指令

index

  • 语法index file ...;
  • 默认值index index.html;
  • 上下文http, server, location

index 指令定义了哪些文件可以被用作索引文件。

  • file:文件名。

    • 文件名中可以包含变量。这意味着你可以根据不同的变量值来动态地查找不同的索引文件,就像示例中的 index.$geo.html 一样。

    • 文件会按照你指定的顺序从左到右依次检查。Nginx 会寻找列表中第一个存在且可访问的文件。

    • 列表中的最后一个元素可以是

      带有绝对路径的文件。例如:

      nginx
      index index.$geo.html index.0.html /index.html;

      在这个例子中,如果前两个文件都找不到,Nginx 会尝试查找根目录下的 /index.html文件。


内部重定向的注意事项

需要特别注意的一点是,使用 index 文件会引起一个内部重定向。这意味着,Nginx 在内部会将对目录的请求(例如 /)转换为对索引文件的请求(例如 /index.html)。这个内部重定向可能会导致请求被不同的 location 块处理。

例如,考虑以下配置:

nginx
location = / { # 精确匹配 /
    index index.html;
}

location / { # 前缀匹配所有请求
    ...
}

在这种配置下:

  1. 当客户端发起一个对 / 的请求时,它会首先被 location = / 这个精确匹配的块捕获。
  2. location = / 块中,index index.html; 指令会被执行。Nginx 会尝试查找 index.html 文件。
  3. 如果 index.html 存在,Nginx 实际上会进行一个内部重定向,将请求视为对 /index.html 的请求。
  4. 由于 /index.html 不再精确匹配 location = /(因为它不是 /),这个内部重定向后的请求就会被第二个 location / 块(前缀匹配所有请求)来处理。

所以,对 / 的请求最终会被第二个 location / 块当作 /index.html 来处理。理解这个内部重定向行为对于编写复杂的 Nginx 配置非常重要,以避免意外的行为。

评论

评论加载中……