Nginx Location 块详解:路由网络流量

Nginx location 块是高效网络流量路由的基石。本指南全面解析五种不同的匹配修饰符(前缀、精确、最长前缀、正则表达式),并详细说明 Nginx 遵循的严格处理顺序。通过实际配置示例,学习如何精确路由静态资源、代理 API 调用以及实施安全规则。掌握 location 块是实现精确流量控制、确保服务器快速性能和稳健配置管理的关键。

Nginx Location 块详解:路由网络流量

Nginx location 块决定了请求进入正确的 server 块后的处理方式。它们使得 /static/app.css 可以从磁盘提供,/api/users 可以代理到应用程序,而 /.git/config 可以在泄露敏感信息之前被拒绝。

大多数 location 块的错误并非语法错误,而是优先级错误。一个正则表达式可能捕获了你期望由前缀块处理的请求。root 路径可能添加了比你预期更多的 URI。带有尾部斜杠的 proxy_pass 与没有尾部斜杠的相同指令相比,会以不同的方式重写上游 URI。以下示例将重点关注这些实际的失败点。

Location 块的作用与结构

location 块定义了 Nginx 应如何根据请求 URI(统一资源标识符)响应请求。这些块始终嵌套在 server 块内。

当客户端发起请求(例如 GET /images/logo.png)时,Nginx 会检查请求 URI 与监听 server 块中定义的所有 location 块,以确定适当的处理方式,例如提供文件、重定向客户端或将请求代理到应用服务器。

基本语法

语法要求一个修饰符(或没有)后跟一个模式(URI):

location [修饰符] [模式] {
    # 配置指令(例如 root, index, proxy_pass)
}

理解 Location 匹配类型(修饰符)

Nginx 提供了一小组 location 匹配样式。选择会影响路由精度以及 Nginx 在选择处理程序之前需要做多少工作。

1. 前缀匹配(无修饰符)

这是默认的匹配类型。Nginx 搜索与请求 URI 匹配的最长起始字符串。

修饰符 示例 行为 最佳用例
(无) location /blog/ 匹配以 /blog/ 开头的 URI(例如 /blog/post/1)。 通用目的,定义网站的大块区域。

示例:

location /docs/ {
    root /var/www/html/public;
    # 如果 URI 是 /docs/manual.pdf,Nginx 会查找 /var/www/html/public/docs/manual.pdf
}

2. 精确匹配(=

此修饰符强制 URI 与模式完全匹配。如果匹配,Nginx 立即停止搜索其他 location。这是最快的匹配类型。

修饰符 示例 行为 最佳用例
= location = /favicon.ico 仅精确匹配 URI /favicon.ico 处理特定的、频繁请求的文件或默认页面。

3. 最长前缀,非正则表达式(^~

这是一种特殊的前缀匹配。如果 Nginx 使用 ^~ 找到最长前缀匹配,它会立即停止检查任何正则表达式 location 块,从而有效地覆盖它们。

修饰符 示例 行为 最佳用例
^~ location ^~ /assets/ 匹配以 /assets/ 开头的 URI,并阻止检查较慢的正则表达式匹配。 快速提供静态资源,并确保资源目录可预测地处理。

4. 区分大小写的正则表达式(~

这使用 Perl 兼容正则表达式(PCRE)进行匹配。它功能强大,但比前缀匹配慢。第一个匹配的正则表达式块获胜。

修饰符 示例 行为 最佳用例
~ location ~ \.php$ 匹配任何以 .php 结尾的 URI。 特定文件类型处理(例如,将 PHP 脚本传递给 PHP-FPM)。

5. 不区分大小写的正则表达式(~*

~ 相同,但匹配忽略 URI 的大小写。

修饰符 示例 行为 最佳用例
~* `location ~* .(jpg gif png)$`

关键的 Location 处理顺序

理解 Nginx 处理 location 块的顺序对于避免意外行为至关重要。Nginx 并非简单地从上到下读取配置文件。它使用严格的层级结构:

  1. 精确匹配(=): Nginx 首先检查所有精确匹配块。如果找到匹配,处理立即停止,并由该块处理请求。
  2. 最长前缀候选: Nginx 找到最长的匹配前缀 location,包括普通前缀 location 和 ^~ location。
  3. ^~ 跳过正则表达式: 如果最佳前缀匹配使用了 ^~,Nginx 使用它并跳过正则表达式检查。
  4. 正则表达式(~~*): 如果最佳前缀不是 ^~,Nginx 按照它们在配置文件中出现的顺序检查正则表达式 location。第一个匹配的正则表达式块获胜。
  5. 最长标准前缀匹配: 如果没有正则表达式匹配获胜,Nginx 使用它已经找到的最长前缀候选。在许多配置中,location / 是最终的备用选项。

这就是为什么 ^~ /static/ 很常见。没有 ^~,后面的正则表达式如 location ~* \.(css|js)$ 仍然可能为 /static/app.css 获胜。有时这没问题。有时它会绕过你期望用于整个 /static/ 目录的缓存头、根路径或访问规则。

实际配置场景

1. 为性能优先处理静态资源

为了确保 Nginx 直接且快速地提供静态文件,防止较慢的正则表达式检查和应用程序服务器的不必要处理,请使用 ^~ 修饰符。

server {
    listen 80;
    server_name myapp.com;

    # 1. 主页的精确匹配(最高优先级)
    location = / {
        proxy_pass http://backend_app_server;
    }

    # 2. 快速处理静态资源,绕过正则表达式检查
    location ^~ /static/ {
        root /var/www/site;
        expires 30d;
    }

    # 3. 常见媒体文件的正则表达式
    location ~* \.(gif|ico|css|js)$ {
        root /var/www/site;
        expires 7d;
    }

    # 4. 所有其他动态请求的备用选项
    location / {
        proxy_pass http://backend_app_server;
    }
}

2. 路由和代理 API 流量

当使用 Nginx 作为反向代理时,location 块将流量引导到正确的上游应用服务器。

location /api/v1/ {
    # 确保 Nginx 尊重客户端连接设置
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;

    # 将所有以 /api/v1/ 开头的流量路由到后端服务
    proxy_pass http://api_backend_service/v1/;

    # 在 proxy_pass 中使用 URI 时,Nginx 会替换匹配的前缀。
    # /api/v1/users 在上游变为 /v1/users。
}

尾部斜杠的行为值得测试。这两个示例是不同的:

location /api/ {
    proxy_pass http://api_backend;
}

location /api/ {
    proxy_pass http://api_backend/;
}

第一种形式将原始 URI(包括 /api/...)传递给上游。第二种形式将匹配的 /api/ 前缀替换为 /。如果你的上游在小的配置编辑后突然开始返回 404,请在责怪应用程序之前检查这一点。

3. 保护敏感目录

Location 块可用于拒绝外部访问敏感的内部目录,例如配置文件或像 .git 这样的隐藏目录。

# 拒绝访问以点开头的文件(隐藏文件)
location ~ /\.(ht|svn|git) {
    deny all;
    return 404; # 返回 404 而不是 403,以避免暴露它们的存在
}

# 拒绝访问特定的配置目录
location /app/config/ {
    deny all;
}

对于隐藏文件,许多团队使用更广泛的拒绝规则:

location ~ /\.(?!well-known/) {
    deny all;
    return 404;
}

这个例外保留了 /.well-known/ 用于诸如 ACME HTTP-01 证书挑战之类的事情,同时仍然阻止大多数点文件。如果你的网站提供其他合法的以点开头的路径,请仔细测试这一点。

安全警告:使用 aliasroot

在 location 块内配置文件路径时,请注意 rootalias 之间的区别。

  • root 将完整的请求 URI 附加到定义的路径。(例如,location /images/ + root /data/ 导致 /data/images/filename.jpg
  • alias 用定义的路径替换 URI 的匹配部分。当 location 块使用正则表达式或需要在提供文件之前剥离部分路径时,这通常是必要的。(例如,location /static/ + alias /opt/app/files/ 导致 /opt/app/files/filename.jpg

4. 处理尾部斜杠和重定向

强制执行一致的 URL 结构通常是可取的,例如确保目录始终以尾部斜杠(/)结尾。

# 如果缺少,强制为目录路径添加尾部斜杠
location ~* /[a-z0-9\-_]+$ {
    # 如果 URI 匹配一个文件,Nginx 将尝试提供它。如果不匹配,则将其视为目录。
    # 检查请求的 URI 是否映射到磁盘上的目录:
    if (-d $request_filename) {
        return 301 $uri/;
    }
}

调试 Location 路由的好方法

当请求到达错误的地方时,将问题简化为一个 URL 和一个 server 块。运行 nginx -T 查看完整的渲染配置,包括包含的文件,然后搜索每个可能匹配该 URI 的 location。特别注意正则表达式块,因为它们在文件中的顺序很重要。

对于静态文件,确认 Nginx 将从 rootalias 构建的文件系统路径。对于代理请求,确认 proxy_pass 是否在上游名称之后包含 URI 部分。然后仅在 nginx -t 通过后重新加载。

一旦你内化了优先级规则,Location 块就是可预测的,但当配置通过复制粘贴增长时,它们是不宽容的。对小的热路径使用精确匹配,对应该绕过正则表达式处理的目录使用 ^~,仅在真正需要模式匹配时使用正则表达式,并使用普通的 location / 作为清晰的备用选项。