Nginx配置文件深度解析:结构、上下文与优化实践
引言
Nginx是一款高性能的网页服务器,负责处理互联网上最大的一些网站的负载。它尤其擅长处理多个并发连接,并且在转发或提供静态内容方面表现出色。在本指南中,我们将着重讨论Nginx配置文件的结构以及如何设计您的文件的一些指导原则。
理解Nginx配置上下文
本指南将涵盖主要的Nginx配置文件结构。该文件的位置取决于Nginx的安装方式。在许多Linux发行版中,该文件位于/etc/nginx/nginx.conf
。如果该位置不存在,也可能位于/usr/local/nginx/conf/nginx.conf
或/usr/local/etc/nginx/nginx.conf
。
当您查看主配置文件时,首先要注意的一点是它以类似于树状结构的方式组织,用一对括号({
和 }
)标记。在Nginx文档中,这些括号定义的区域被称为“上下文”,因为它们包含根据关注领域划分的配置细节。这种划分提供了组织结构,并带有一些条件逻辑,用于决定是否应用其中的配置。
由于上下文的层次可以嵌套在一起,Nginx允许继承配置。一般规则是,如果一个指令在多个嵌套范围内都是有效的,则在更广泛的上下文中声明的值将作为默认值传递给任何子上下文。子上下文可以覆盖这些值。需要注意的是,对于任何数组类型的指令的覆盖将替换先前的值,而不是添加到它。
指令只能在它们被设计用于的上下文中使用。当Nginx读取包含在错误上下文中声明的指令的配置文件时,会抛出错误。Nginx文档包含了每个指令可用于的上下文信息,这使其成为一个有用的参考。
在下面,我们将讨论在使用Nginx时最常见的情境。
核心上下文
我们将讨论的第一组上下文是Nginx用于创建层次树并分离不同配置块的核心上下文。这些上下文组成了Nginx配置的主要结构。
主上下文
最常见的情境是“主”或“全局”情境。这是唯一一个不包含在典型情境块内的情境。
/etc/nginx/nginx.conf
的解释:
# 主上下文在此,位于任何其他上下文之外
. . .
上下文 {
. . .
}
只要完全位于这些块之外的任何指令都属于“主”上下文。请记住,如果您的Nginx配置以模块化方式设置,即在多个文件中具有配置选项,那么一些文件包含的指令看起来可能存在于括号上下文之外,但在配置加载时将包含在上下文中。
主上下文代表了Nginx配置的最广泛环境。它用于配置影响整个应用程序的细节。尽管本节中的指令会影响到较低的上下文,但其中许多指令无法在较低层级中被覆盖。
在主上下文中配置的一些常见细节包括运行工作进程的系统用户和组、工作进程的数量以及保存主Nginx进程ID的文件。可以在此级别设置整个应用程序的默认错误文件(可以在更具体的上下文中覆盖此设置)。
事件上下文
“事件”上下文被包含在“主”上下文中。它用于设置全局选项,影响Nginx在一般水平上处理连接的方式。在Nginx配置中只能定义一个事件上下文。
在配置文件中,此上下文将以以下方式显示,不属于任何其他括号上下文之内。
/etc/nginx/nginx.conf
可以被简单地翻译为“/etc/nginx/nginx
配置文件”。
# 主上下文
events {
# 事件上下文
. . .
}
Nginx采用基于事件的连接处理模型,所以在此上下文中定义的指令决定了工作进程应如何处理连接。主要的指令可以用来选择连接处理技术或修改这些方法的实现方式。
通常情况下,平台会自动选择最高效的连接处理方法。对于Linux系统而言,epoll 方法通常是最佳选择。
其他可配置的项目包括每个工作人员能处理的连接数量、一个工作人员是否一次只能处理一个连接或在接到待处理的连接通知后处理所有待处理的连接,以及工作人员是否轮流响应事件。
HTTP 上下文
定义HTTP上下文可能是Nginx最常见的用途。在配置Nginx作为Web服务器或反向代理时,“http”上下文将包含大部分配置。该上下文将包含所有指令和其他上下文,用于定义程序如何处理HTTP或HTTPS连接。
Http上下文是事件上下文的兄弟节点,因此它们应该并列列出,而不是嵌套在一起。它们都是主要上下文的子节点。
/etc/nginx/nginx.conf
可以被意译为“/etc/nginx/nginx.conf
配置文件”。
# 主上下文
events {
# 事件上下文
. . .
}
http {
# http 上下文
. . .
}
在较低的上下文中,更具体地处理请求的方式,而在这一层级上的指令控制了每个虚拟服务器的默认设置。大量的指令可以在这个上下文及以下级别进行配置,这取决于您希望继承的方式。
你可能会遇到的一些指令控制着访问日志和错误日志的默认位置(access_log
和error_log
),配置文件操作的异步I/O(aio
,sendfile
和directio
),以及错误发生时服务器的状态配置(error_page
)。其他指令配置压缩(gzip
和gzip_disable
),微调TCP保持活动设置(keepalive_disable
,keepalive_requests
和keepalive_timeout
),以及Nginx遵循的尝试优化数据包和系统调用的规则(sendfile
,tcp_nodelay
和tcp_nopush
)。额外的指令配置应用程序级别的文档根目录和索引文件(root
和index
),并设置用于存储不同类型数据的各种哈希表(*_hash_bucket_size
和*_hash_max_size
用于server_names
,types
和variables
)。更多信息请参考Nginx文档。
服务器上下文
“server”上下文是在“http”上下文中声明的。这是我们第一个嵌套的、有括号的上下文示例。这也是第一个允许多个声明的上下文。
服务器上下文的一般格式可能如下所示。请记住,这些位于http上下文中。
/etc/nginx/nginx.conf
可以被翻译为: /etc/nginx/nginx.conf
# 主上下文
http {
# http 上下文
server {
# 第一个服务器上下文
}
server {
# 第二个服务器上下文
}
}
你可以声明多个服务器上下文,因为每个实例定义了一个处理客户端请求的特定虚拟服务器。你可以拥有任意数量的服务器模块,每个模块可以处理一组特定的连接子集。
这种上下文类型也是Nginx必须使用来选择算法的第一种类型。每个客户端请求将根据单个服务器上下文中定义的配置进行处理,因此Nginx必须根据请求的细节来决定哪个服务器上下文最合适。有两种常见的选项,它们在使用域名上有所区别。
- listen: 此服务器块旨在响应的IP地址/端口组合。如果客户端发出的请求与这些值匹配,则此块可能会被选中来处理连接。
- server_name: 此指令是用于选择服务器块进行处理的另一个组件。如果存在多个具有相同特异性
listen
指令的服务器块可以处理请求,Nginx将解析请求的“Host”头并将其与此指令进行匹配。
在这个环境中,指令可以覆盖许多可能在HTTP环境中定义的指令,包括日志记录、文档根目录、压缩等。除了从HTTP上下文中获取的指令外,我们还可以配置文件来尝试响应请求(通过try_files
指令),发出重定向和重写(return
和rewrite
指令),以及设置任意变量(set
指令)。
位置上下文
你将经常面对的下一个上下文是位置上下文。位置上下文与服务器上下文有许多关系性特征相似。例如,可以定义多个位置上下文,每个位置用于处理特定类型的客户端请求,通过选择算法,通过将位置定义与客户端请求进行匹配来选择每个位置。
虽然决定是否选择服务器块的指令是在服务器上下文中定义的,但决定某个位置是否能处理请求的组件位于位置定义中(即开启位置块的那一行)。
一般的语法样式如下:
/etc/nginx/nginx.conf
可以被改写为:Nginx配置文件路径为 /etc/nginx/nginx.conf
。
location 匹配修饰符 位置匹配 {
. . .
}
位置块(location blocks)存在于服务器上下文中,它与服务器块不同的是,可以互相嵌套。这对于创建一个更通用的位置上下文以捕捉特定的流量子集,然后根据更具体的标准在内部的其他上下文中进一步处理它非常有用。
/etc/nginx/nginx.conf
可以翻译为“Nginx配置文件”。# main context
server {
# server context
location /match/criteria {
# first location context
}
location /other/criteria {
# second location context
location nested_match {
# first nested location
}
location other_nested {
# second nested location
}
}
}
服务器上下文根据请求的IP地址/端口组合和“Host”头中的主机名进行选择,而定位块则通过查看请求URI在服务器块内进一步细分请求处理。请求URI是在域名或IP地址/端口组合之后的请求部分。
例如,假设一个客户端请求使用80端口的http://www.example.com/blog
。Nginx会分别使用http
、www.example.com
和端口80
来确定选择哪个服务器块。选择服务器后,会使用定义的位置来评估请求URI中的/blog
部分,以确定响应请求所需使用的进一步上下文。
在位置上下文中,您可能会看到许多指令,这些指令也可以在父级别上使用。此级别上的新指令允许您访问文档根目录之外的位置(别名),将位置标记为仅内部可访问(内部),以及代理到其他服务器或位置(使用HTTP、FastCGI、SCGI和uWSGI代理)。
其他上下文
尽管上述例子展示了您在使用Nginx时会遇到的基本上下文,但还存在其他一些上下文。以下上下文仅在特定情况下使用,或者用于大多数人不会使用的功能:
- split_clients: 此上下文用于根据百分比将服务器接收到的客户端按变量进行标记分类。这些分类可用于A/B测试,通过向不同主机提供不同内容。
- perl / perl_set: 这些上下文配置其所在位置的Perl处理器。仅用于Perl处理。
- map: 此上下文用于根据另一个变量的值来设置变量的值。它提供了一个变量值到另一个变量值的映射,以确定第二个变量应如何设置。
- geo: 类似于上述上下文,此上下文用于指定映射。然而,此映射专门用于对客户端IP地址进行分类。它根据连接的IP地址设置变量的值。
- types: 此上下文再次用于映射。它用于将MIME类型映射到与其关联的文件扩展名。这通常通过Nginx提供的一个文件实现,该文件被引入到主
nginx.conf
配置文件中。 - charset_map: 这是另一个映射上下文的例子。此上下文用于映射从一个字符集到另一个字符集的转换表。在上下文头部,列出两个字符集,在主体中进行映射。
上游上下文
上游上下文用于定义和配置“上游”服务器。该上下文定义了一个被命名的服务器池,Nginx可以将请求代理到这个池中的服务器上。当您配置不同类型的代理时,很可能会使用这个上下文。
上游上下文应该放置在http
上下文中,而不是任何特定的服务器上下文之外。形式如下:
# main context
http {
# http context
upstream upstream_name {
# upstream context
server proxy_server1;
server proxy_server2;
. . .
}
server {
# server context
}
}
然后可以通过名称在服务器或位置块内引用上游上下文,将特定类型的请求传递给已定义的服务器池。上游将使用算法(默认为轮询)来确定要将请求交给哪个特定的服务器。此上下文使Nginx在代理请求时能够进行负载均衡。
if上下文
“if”上下文可以建立以提供指令的条件处理。就像传统编程中的if语句一样,Nginx中的if指令将在给定的测试返回“true”时执行包含的指令。
在Nginx中,if上下文是由rewrite模块提供的,这也是此上下文的主要预期用途。由于Nginx将使用许多其他专用指令来测试请求的条件,因此if
不应用于大多数形式的条件执行。这是一个非常重要的问题,以至于Nginx社区创建了一个名为“if is evil”的页面。
问题在于Nginx的处理顺序经常会导致意料之外的结果。在这些情况下,只有return
和rewrite
指令被认为是可靠且安全的。另外,当使用if上下文时要注意,它会使同一上下文中的try_files
指令无效。
大多数情况下,会使用一个if语句来确定是否需要重新编写或返回。这些if语句通常存在于位置块中,所以常见的形式会类似于这样:
# main context
http {
# http context
server {
# server context
location location_match {
# location context
if (test_condition) {
# if context
}
}
}
}
limit_except指令所指的上下文
limit_except
上下文用于限制位置上下文中某些HTTP方法的使用。例如,如果只有特定的客户端应该有权限访问POST内容,而每个人都应该有读取内容的能力,可以使用limit_except
块来定义此要求。
以上例子将会看起来像这样:
. . .
# server or location context
location /restricted-write {
# location context
limit_except GET HEAD {
# limit_except context
allow 192.168.1.1/24;
deny all;
}
}
当遇到除了上下文标题列出的HTTP方法以外的任何HTTP方法时,此指令将适用于上下文中的指令(用于限制访问)。上述示例的结果是,任何客户端都可以使用GET和HEAD动词,但只有来自192.168.1.1/24子网的客户端可以使用其他方法。
关于上下文,要遵守的一般规则
现在您已经了解了在探索Nginx配置时可能遇到的常见上下文,我们可以讨论在处理Nginx上下文时使用的一些最佳实践。
在最高上下文中应用指令
许多指令适用于多个上下文环境。例如,有很多指令可以放置在http、server或location的上下文中。这使得我们在设置这些指令时具有灵活性。
一般而言,最好将指令声明在其适用的最高上下文中,并根据需要在较低上下文中进行覆盖。这是因为Nginx实施了继承模型。使用这种策略有许多原因。
首先,高层次的声明使得你可以在兄弟上下文之间避免不必要的重复。例如,在下面的例子中,每个位置声明了相同的文档根部。
这是文章《了解Nginx配置文件结构和配置上下文》的第3部分(共3部分)。
http {
server {
location / {
root /var/www/html;
. . .
}
location /another {
root /var/www/html;
. . .
}
}
}
您可以将根路径(root
)指令移动到服务器块(server
)中,甚至移动到HTTP块(http
)中,如下所示:
http {
root /var/www/html;
server {
location / {
. . .
}
location /another {
. . .
}
}
}
大多数情况下,在服务器级别声明是最合适的,但在更高级别声明也有其优点。这不仅可以减少您设置指令的位置,还可以将默认值级联到所有子元素,从而防止因在较低级别遗漏指令而导致错误。这对于长配置来说可能是一个重大问题。在更高级别声明可以为您提供一个良好的默认设置。
使用多个兄弟上下文而非if
逻辑进行处理
当您想根据客户端请求中的某些信息来区别处理请求时,用户通常会尝试使用if
上下文进行条件化处理。我们之前简要提到了这个问题的几个方面。
首先是if
指令经常产生与管理员预期不符的结果。尽管在相同的输入下处理总是会得到相同的结果,但Nginx解释环境的方式可能与未经大量测试的假设不同。
第二个原因是,Nginx已经为许多这些目的优化并专门制定了指令。Nginx使用了一种经过充分记录的选择算法,用于选择服务器块和位置块等任务。如果可能的话,最好将不同的配置移动到它们自己的块中,以便该算法可以处理选择过程的逻辑。
举个例子,不要依赖于重写(rewrite
)来将用户提供的请求转换成您想要处理的格式,您应该尝试设置两个块来处理请求:其中一个代表所需的方法,另一个用于捕获混乱的请求并将它们重定向(可能还要重写)到正确的块中。
结果通常更易读,并且具有更高的性能优势。正确的请求不需要进行任何额外的额外处理,而且在许多情况下,不正确的请求可以通过重定向而不是重写来处理,这样可以降低开销。
结论
到这个阶段,您应该已经对Nginx最常见的上下文和定义它们的指令有了很好的掌握。
请始终查阅Nginx的官方文档以获取指令可放置的上下文信息,并评估最有效的位置。在创建配置时要小心,这将提高可维护性,并且通常会提高性能。
接下来,您可以学习如何在Nginx中配置密码身份验证。