介绍
Nginx 是一个高性能的 Web 服务器,负责处理 Internet 上一些最大站点的负载。 它特别擅长处理许多并发连接,并且擅长提供静态内容。
虽然许多用户都知道 Nginx 的功能,但新用户经常对他们在 Nginx 配置文件中找到的一些约定感到困惑。 在本指南中,我们将重点讨论 Nginx 配置文件的基本结构以及有关如何设计文件的一些指南。
了解 Nginx 配置上下文
本指南将介绍 Nginx 主配置文件中的基本结构。 此文件的位置将根据您在计算机上安装软件的方式而有所不同。 对于许多发行版,该文件将位于 /etc/nginx/nginx.conf
。 如果那里不存在,也可能在 /usr/local/nginx/conf/nginx.conf
或 /usr/local/etc/nginx/nginx.conf
处。
在查看主配置文件时,您首先应该注意到的一件事是,它似乎以树状结构组织,由一组括号定义(看起来像 {
和 [ X214X])。 在 Nginx 用语中,这些括号定义的区域称为“上下文”,因为它们包含根据其关注区域分开的配置详细信息。 基本上,这些部门提供了一个组织结构以及一些条件逻辑来决定是否应用其中的配置。
因为上下文可以相互分层,Nginx 提供了一层指令继承。 作为一般规则,如果指令在多个嵌套范围内有效,则更广泛上下文中的声明将作为默认值传递给任何子上下文。 子上下文可以随意覆盖这些值。 值得注意的是,对任何数组类型指令的覆盖将 替换 以前的值,而不是附加到它。
指令只能在为其设计的上下文中使用。 Nginx 在读取带有在错误上下文中声明的指令的配置文件时会出错。 Nginx 文档 包含有关每个指令在哪些上下文中有效的信息,因此如果您不确定,它是一个很好的参考。
下面,我们将讨论您在使用 Nginx 时可能遇到的最常见的上下文。
核心语境
我们将讨论的第一组上下文是 Nginx 用来创建分层树并分离离散配置块的关注点的核心上下文。 这些是构成 Nginx 配置主要结构的上下文。
主要背景
最一般的上下文是“主要”或“全局”上下文。 它是唯一不包含在典型上下文块中的上下文,如下所示:
# The main context is here, outside any other contexts . . . context { . . . }
任何完全存在于这些块之外的指令都被称为“主”上下文。 请记住,如果您的 Nginx 配置以模块化方式设置,则某些文件将包含看似存在于括号上下文之外的指令,但当配置拼接在一起时,这些指令将包含在此类上下文中。
主要上下文代表了 Nginx 配置的最广泛环境。 它用于配置在基本级别上影响整个应用程序的详细信息。 虽然本节中的指令影响较低的上下文,但其中许多不是 inherited 因为它们不能在较低级别中被覆盖。
在主上下文中配置的一些常见细节是运行工作进程的用户和组、工作进程的数量以及保存主进程 PID 的文件。 您甚至可以定义诸如工作 CPU 亲和性和工作进程的“友好性”之类的东西。 整个应用程序的默认错误文件可以设置在这个级别(这可以在更具体的上下文中被覆盖)。
事件上下文
“事件”上下文包含在“主”上下文中。 它用于设置全局选项,这些选项会影响 Nginx 在一般级别上处理连接的方式。 Nginx 配置中只能定义一个事件上下文。
此上下文在配置文件中将如下所示,在任何其他括号上下文之外:
# main context events { # events context . . . }
Nginx 使用基于事件的连接处理模型,因此在此上下文中定义的指令决定了工作进程应如何处理连接。 主要是,此处找到的指令用于选择要使用的连接处理技术,或修改这些方法的实现方式。
通常,连接处理方法是根据平台可用的最有效选择自动选择的。 对于 Linux 系统,epoll
方法通常是最佳选择。
其他可以配置的项目是每个worker可以处理的连接数,worker是一次只接受一个连接还是在收到挂起连接的通知后获取所有挂起的连接,以及worker是否会轮流响应事件.
HTTP 上下文
将 Nginx 配置为 Web 服务器或反向代理时,“http”上下文将包含大部分配置。 此上下文将包含定义程序如何处理 HTTP 或 HTTPS 连接所需的所有指令和其他上下文。
http 上下文是事件上下文的兄弟,因此它们应该并排列出,而不是嵌套。 他们都是主要上下文的孩子:
# main context events { # events context . . . } http { # http context . . . }
虽然较低的上下文更具体地说明如何处理请求,但此级别的指令控制其中定义的每个虚拟服务器的默认值。 在此上下文及以下上下文中可以配置大量指令,具体取决于您希望继承如何运行。
您可能遇到的一些指令控制访问和错误日志的默认位置(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
)。
服务器上下文
“服务器”上下文在“http”上下文中声明为。 这是我们第一个嵌套括号上下文的例子。 它也是第一个允许多个声明的上下文。
服务器上下文的一般格式可能看起来像这样。 请记住,这些驻留在 http 上下文中:
# main context http { # http context server { # first server context } server { # second server context } }
允许服务器上下文的多个声明的原因是每个实例都定义了一个特定的虚拟服务器来处理客户端请求。 您可以根据需要拥有任意数量的服务器块,每个块都可以处理特定的连接子集。
由于多个服务器块的可能性和可能性,这种上下文类型也是 Nginx 必须使用选择算法做出决策的第一个。 每个客户端请求都将根据单个服务器上下文中定义的配置进行处理,因此 Nginx 必须根据请求的详细信息决定哪个服务器上下文最合适。 决定是否使用服务器块来回答请求的指令是:
- listen:此服务器块旨在响应的 IP 地址/端口组合。 如果客户端发出与这些值匹配的请求,则可能会选择此块来处理连接。
- server_name:该指令是用于选择服务器块进行处理的另一个组件。 如果有多个具有相同特性的监听指令可以处理请求的服务器块,Nginx 将解析请求的“Host”标头并将其与该指令匹配。
此上下文中的指令可以覆盖许多可能在 http 上下文中定义的指令,包括日志记录、文档根目录、压缩等。 除了从 http 上下文中获取的指令之外,我们还可以配置文件以尝试响应请求(try_files
),发出重定向和重写(return
和 [X187X ]),并设置任意变量(set
)。
位置上下文
您将定期处理的下一个上下文是位置上下文。 位置上下文与服务器上下文共享许多关系质量。 例如,可以定义多个位置上下文,每个位置用于处理某种类型的客户端请求,通过选择算法将位置定义与客户端请求匹配来选择每个位置。
虽然确定是否选择服务器块的指令在服务器 context 中定义,但决定位置处理请求能力的组件位于位置 definition(打开位置块的行)。
一般语法如下所示:
location match_modifier location_match { . . . }
位置块存在于服务器上下文中,并且与服务器块不同,它可以相互嵌套。 这对于创建更一般的位置上下文以捕获特定流量子集很有用,然后根据更具体的标准进一步处理它,其中包含额外的上下文:
# 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 地址/端口组合和“主机”标头中的主机名选择的,但位置块通过查看请求 URI 进一步划分服务器块内的请求处理。 请求 URI 是位于域名或 IP 地址/端口组合之后的请求部分。
因此,如果客户端在端口 80 上请求 http://www.example.com/blog
,则 http
、www.example.com
和端口 80 都将用于确定选择哪个服务器块。 选择服务器后,将根据定义的位置评估 /blog
部分(请求 URI),以确定应使用哪个进一步的上下文来响应请求。
您可能在位置上下文中看到的许多指令在父级也可用。 此级别的新指令允许您访问文档根目录之外的位置 (alias
),将该位置标记为只能在内部访问 (internal
),并代理到其他服务器或位置(使用 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 能够在代理请求时进行一些负载平衡。
邮件上下文
虽然 Nginx 最常用作 Web 或反向代理服务器,但它也可以用作高性能邮件代理服务器。 用于这种类型指令的上下文被恰当地称为“邮件”。 邮件上下文是在“主”或“全局”上下文中定义的(在 http 上下文之外)。
邮件上下文的主要功能是在服务器上提供一个用于配置邮件代理解决方案的区域。 Nginx 能够将身份验证请求重定向到外部身份验证服务器。 然后它可以提供对 POP3 和 IMAP 邮件服务器的访问,以提供实际的邮件数据。 如果需要,还可以将邮件上下文配置为连接到 SMTP 中继主机。
一般来说,邮件上下文看起来像这样:
# main context events { # events context } mail { # mail context }
如果上下文
可以建立“if”上下文以提供对其中定义的指令的条件处理。 与传统编程中的 if 语句一样,Nginx 中的 if 指令将在给定测试返回“true”时执行包含的指令。
Nginx 中的 if 上下文由 rewrite 模块提供,这是该上下文的主要用途。 由于 Nginx 将使用许多其他专用指令测试请求的条件,如果 not 应该用于大多数形式的条件执行。 这是一个非常重要的注意事项,以至于 Nginx 社区创建了一个名为 if is evil 的页面。
问题基本上是 Nginx 的处理顺序经常会导致意想不到的结果,这似乎颠覆了 if 块的含义。 唯一被认为可以在这些上下文中安全使用的指令是 return
和 rewrite
指令(创建此上下文的指令)。 使用 if 上下文时要记住的另一件事是,它会使同一上下文中的 try_files
指令变得无用。
大多数情况下,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 方法 除外 时,这将在上下文中应用指令(旨在限制访问)。 上述示例的结果是任何客户端都可以使用 GET 和 HEAD 动词,但只有来自 192.168.1.1/24
子网的客户端才允许使用其他方法。
关于上下文应遵循的一般规则
现在您已经了解了在探索 Nginx 配置时可能会遇到的常见上下文,我们可以讨论一些在处理 Nginx 上下文时使用的最佳实践。
在可用的最高上下文中应用指令
许多指令在不止一种情况下有效。 例如,有很多指令可以放在 http、服务器或位置上下文中。 这使我们可以灵活地设置这些指令。
但是,作为一般规则,通常最好在它们适用的最高上下文中声明指令,并在必要时在较低的上下文中覆盖它们。 这是可能的,因为 Nginx 实现了继承模型。 使用此策略的原因有很多。
首先,在高层声明可以避免兄弟上下文之间不必要的重复。 例如,在下面的示例中,每个位置都声明了相同的文档根:
http { server { location / { root /var/www/html; . . . } location /another { root /var/www/html; . . . } } }
您可以将根移到 server 块,甚至移到 http 块,如下所示:
http { root /var/www/html; server { location / { . . . } location /another { . . . } } }
大多数时候,服务器级别是最合适的,但在更高级别声明有其优势。 这不仅允许您在更少的位置设置指令,还允许您将默认值级联到所有子元素,防止您因忘记较低级别的指令而遇到错误的情况。 这可能是长配置的主要问题。 在更高级别声明为您提供了一个健全的默认值。
使用多个同级上下文而不是 If 逻辑进行处理
当您想根据可以在客户端请求中找到的某些信息以不同方式处理请求时,用户通常会跳转到“if”上下文以尝试条件化处理。 有几个问题我们在前面简要地谈到过。
首先是“if”指令经常返回不符合管理员期望的结果。 尽管在给定相同输入的情况下处理总是会导致相同的结果,但 Nginx 解释环境的方式可能与未经大量测试所假设的方式大不相同。
造成这种情况的第二个原因是已经有优化的、专用的指令用于许多这些目的。 Nginx 已经为诸如选择服务器块和位置块之类的事情提供了一个有据可查的选择算法。 因此,如果可能,最好尝试将您的不同配置移动到各自的块中,以便该算法可以处理选择过程逻辑。
例如,不要依赖重写来将用户提供的请求转换为您想要使用的格式,您应该尝试为请求设置两个块,一个代表所需的方法,另一个代表捕获杂乱的请求并将它们重定向(并可能重写)到您的正确块。
结果通常更容易阅读,并且还具有更高性能的额外好处。 正确的请求不经过额外的处理,在许多情况下,不正确的请求可以通过重定向而不是重写来解决,这应该以较低的开销执行。
结论
至此,您应该很好地掌握了 Nginx 最常见的上下文以及创建定义它们的块的指令。
始终检查 Nginx 的文档 以获取有关可以将指令放置在哪些上下文中以及评估最有效位置的信息。 在创建配置时要小心,不仅会增加可维护性,而且通常还会提高性能。