Skip to content

Nginx 配置浅析

作者:Atom
字数统计:3.9k 字
阅读时长:15 分钟

本文将主要分析 NginxWeb 应用中的常用配置, 笔者将文章结构分配如下:

  • 概念
  • 常用指令
  • 应用实践

概念

Nginx 是一个轻量级、高性能、稳定性高、并发性好的 HTTP 和反向代理服务器; 它具有有很多非常优越的特性, 应用非常广泛

  • 反向代理
  • 负载均衡
  • HTTP 服务器(包含动静分离)
  • 正向代理

常用配置指令

location

概念

location 实现了对请求的细分处理,有些 URI 返回静态内容,有些分发到后端服务器等, 支持的语法 location [=|~|~*|^~|@] pattern { ... };

nginx
# 修饰符 = : 要求路径完全匹配<精确匹配>
location = /abc {}

# 修饰符 ~ : 区分大小写的正则匹配<正则匹配>
location ~ ^/abcd$ {}

# 修饰符 ~* : 不区分大小写的正则匹配<正则匹配>
location ~* ^/abcd$  {}

# 修饰符 ^~ : 匹配成功立刻停止<前缀匹配>
location ^~ /abcd  {}

# 无修饰符  : 最长匹配<前缀匹配>
location /document {}

以上多个匹配符都有的情况下, 匹配符的优先级是先精确匹配,没有则查找带有 ^~的前缀匹配,没有则进行正则匹配,最后才返回最长匹配下的前缀匹配结果, 多个最长匹配规则相同以书写顺序先后

可以使用以下伪代码来理解这个匹配顺序

nginx
function match(uri):
  rv = NULL

  if uri in exact_match:
    return exact_match[uri]

  if uri in prefix_match:
    if prefix_match[uri] is '^~':
      return prefix_match[uri]
    else:
      rv = prefix_match[uri] // 注意这里没有 return,且这里是最长匹配

  if uri in regex_match:
    return regex_match[uri] // 按文件中顺序,找到即返回
  return rv

rewrite

概念 & 语法

rewrite 实现了用正则表达式(PCRE)改变请求的 URI,返回重定向,并有条件地选择配置

语法: rewrite regex replacement [flag]

作用域

rewrite的作用域可以出现在 server, location, if

匹配顺序

rewrite 规则优先级:

md
执行 server 块的 rewrite 指令 -> 执行 location 匹配 -> 执行指定的 locaiton 中的 rewrite

flag 选项

flag 有以下几个选项

字段作用
last停止 rewrite 检测,开始搜索与更改后的 URI 相匹配的 location
break停止 rewrite 检测,跳出不再检测
redirect返回 302 临时重定向,地址栏会显示跳转后的地址
permanent返回 301 永久重定向,地址栏会显示跳转后的地址(浏览器下次直接访问重定向后的地址

注意点

rewrite在配置中, 重写字符串, 带上协议 http 与不带协议处理方式是不一样的:

sh
# 第一种情况
location / {
    # 当匹配 正则表达式 /test1/(.*)时 请求将被临时重定向到 http://www.$1.com
    # 相当于 flag 写为 redirect
    rewrite /test1/(.*) http://www.$1.com;
    return 200 "ok";
}
# 在浏览器中输入 127.0.0.1:8080/test1/baidu
# 则临时重定向到 www.baidu.com
# 后面的 return 指令将没有机会执行了

# 第二种情况
location / {
    rewrite /test1/(.*) www.$1.com;
    return 200 "ok";
}
# 发送请求如下
# curl 127.0.0.1:8080/test1/baidu
# ok

# 此处没有带http:// 所以只是简单的重写。请求的 uri 由 /test1/baidu 重写为 www.baidu.com
# 因为会顺序执行 rewrite 指令 所以 下一步执行 return 指令 响应了 ok

try_files

概念

try_files 指令是按顺序检测文件的存在性,并且返回第一个找到文件的内容,如果第一个找不到就会自动找第二个,依次查找.其实现的是内部跳转.以下举例说明:

nginx
location /abc {
  # 检测文件4.html和5.html,如果存在正常显示,不存在就去查找@qwe值
  try_files /4.html /5.html @qwe;
}

location @qwe  {
  # 跳转到百度页面
  rewrite ^/(.*)$   http://www.baidu.com;
}

root

概念

root 指令用于指定静态文件的根目录,将请求的 URI 附加到指定的目录路径后面,保持完整的 URI 结构

  • 作用:将请求的 URI 附加到指定的目录路径后面

  • 语法root /path/to/directory;

  • 作用域:可以出现在 httpserverlocationif 块中

请求 /static/file.css 会映射到 /var/www/static/file.css

nginx
location /static/ {
    root /var/www;
}

实际应用场景:

nginx
# 静态资源目录配置
location ~* \.(js|css|png|jpg|gif|ico|svg)$ {
    root /home/www/static;
    expires 1y;
    add_header Cache-Control "public, immutable";
}

# 多级目录结构
location /docs/ {
    root /var/www/project;
    try_files $uri $uri/ =404;
}

alias

概念

alias 指令用于指定静态文件的别名目录,用指定的路径替换 location 匹配的部分,可以实现路径重写和别名功能

  • 作用:用指定的路径替换 location 匹配的部分
  • 语法alias /path/to/directory;
  • 作用域:只能出现在 location 块中

请求 /static/file.css 会映射到 /var/www/static/file.css

nginx
location /static/ {
    alias /var/www/static/;
}

实际应用场景

nginx
# 路径别名配置
location /images/ {
    alias /var/www/uploads/;
    expires 30d;
}

# 版本化资源访问
location /v1/assets/ {
    alias /var/www/current/assets/;
    add_header X-Version "1.0.0";
}

# 多语言资源
location /en/ {
    alias /var/www/locales/en/;
}

root 与 alias 的核心区别 —— 拼接 vs 替换

root拼接alias替换,两者的路径计算公式如下:

text
root:  实际路径 = root值 + 完整URI
alias: 实际路径 = alias值 + (URI - location匹配部分)

以请求 /files/doc.pdf 为例:

nginx
# root —— location 路径被保留
location /files/ {
    root /var/www;
    # /var/www + /files/doc.pdf = /var/www/files/doc.pdf
}

# alias —— location 路径被替换
location /files/ {
    alias /var/www/documents/;
    # /var/www/documents/ + doc.pdf = /var/www/documents/doc.pdf
}
维度rootalias
路径处理拼接:root + URI替换:alias + (URI - location)
适用场景目录结构与 URI 一致需要映射到不同的物理路径
作用域httpserverlocationiflocation
末尾斜杠无影响必须带 /,否则路径拼接出错

alias 末尾斜杠

nginx
location /images/ {
    alias /data/pics/;   # /images/cat.jpg → /data/pics/cat.jpg(正确)
}

location /images/ {
    alias /data/pics;    # /images/cat.jpg → /data/picscat.jpg(错误)
}

:::

add_header

概念

add_header 作用是给响应加上一个头信息(响应头),在 Web 的常见用法是设置控制缓存,配置跨域等相关 request header

nginx
location ~* \.(js|css|png|jpg|gif)$ {
    add_header Cache-Control no-store;
}

proxy_pass

概念

proxy_pass 将请求转发给上游服务器,是反向代理的核心指令。路径传递行为取决于 proxy_pass 后面是否带有 URI 部分

  • 语法proxy_pass URL;
  • 作用域locationiflimit_except 块中

路径传递规则

proxy_pass 后面有没有 URI(包括单独的 /),决定了两种完全不同的行为:

text
不带 URI: 后端收到路径 = 客户端原始路径(透传)
带 URI:   后端收到路径 = proxy_pass的URI + (原始路径 - location匹配部分)

不带 URI —— 透传

nginx
location /api/v1/ {
    proxy_pass http://backend:8080;
}
text
客户端请求:     /api/v1/users
location 匹配: /api/v1/
proxy_pass:    http://backend:8080       (无 URI,触发透传)

后端收到:      /api/v1/users             (原始路径原样转发)

带 URI —— 替换(与 alias 同理)

nginx
location /api/v1/ {
    proxy_pass http://backend:8080/;
}
text
客户端请求:     /api/v1/users
location 匹配: /api/v1/
proxy_pass:    http://backend:8080/      (URI = "/",触发替换)

剥离匹配部分:  /api/v1/users - /api/v1/ = users
拼接 URI:      / + users = /users

后端收到:      /users

带 URI —— 替换前缀

nginx
location /old-api/ {
    proxy_pass http://backend:3000/new-api/;
}
text
客户端请求:     /old-api/users
location 匹配: /old-api/
proxy_pass:    http://backend:3000/new-api/  (URI = "/new-api/",触发替换)

剥离匹配部分:  /old-api/users - /old-api/ = users
拼接 URI:      /new-api/ + users = /new-api/users

后端收到:      /new-api/users

多级代理链路分析

微服务架构中常见多级 Nginx 代理,每一级的路径替换规则会叠加,需要逐级推算:

目标: 客户端请求 /a/b/c/d/e/f,最终命中 B 的 location /c/d/e/f

错误配置一:A 的 proxy_pass 不带 URI

nginx
# Nginx A
location /a/b/c {
    proxy_pass http://B_host:B_port;
}

错误配置二:A 剥离了过多路径

nginx
# Nginx A
location /a/b/c/ {
    proxy_pass http://B_host:B_port/;
}

正确配置:精确计算剥离层级

nginx
# Nginx A
location /a/b/ {
    proxy_pass http://B_host:B_port/;
}

# Nginx B
location /c/d/e/f {
    proxy_pass http://backend:8080;
}

多级代理路径推算方法

注意事项

  1. 尾部斜杠一致性location /api/proxy_pass http://backend/ 的尾部斜杠要配对,否则可能出现双斜杠 // 或路径缺失
  2. 正则 location 不支持 URI 替换:当 location 使用正则匹配(~~*)时,proxy_pass 不能带 URI 部分,否则 Nginx 会报错
  3. rewrite + proxy_pass:如果 location 内有 rewrite ... break,则 proxy_pass 的 URI 部分会被忽略,转发的是 rewrite 后的路径
nginx
# 正则 location —— proxy_pass 不能带 URI
location ~ ^/api/(.*)$ {
    proxy_pass http://backend:8080;       # 正确
    # proxy_pass http://backend:8080/;    # 报错
}

# rewrite + proxy_pass
location /api/ {
    rewrite ^/api/(.*)$ /v2/$1 break;
    proxy_pass http://backend:8080;
    # 实际转发 rewrite 后的 /v2/xxx,而非 /api/xxx
}

proxy_set_header

概念

proxy_set_header 允许重新定义或添加字段传递给代理服务器的请求头(请求头)。该值可以包含文本、变量和它们的组合

在没有定义 proxy_set_header 时会继承之前定义的值。默认情况下,只有两个字段被重定义:

nginx
proxy_set_header Host $proxy_host;
proxy_set_header Connection close;

概念

代理跨域服务中的 Set-Cookie, 例如 a.com 调用 b.com 中的服务, b.com 的服务中设置了 Set-Cookie:xxx; domain:b.com,导致 a.com 无法设置 cookie

nginx
location ~ /xxx/ {
  # b.com和a.com都可以使用regrex表示, eg: ~\.?b.com
  proxy_cookie_domain b.com a.com;
  # 代理 以/sub/ 开头的路径才能访问cookie
  proxy_cookie_path /sub/ /;
  proxy_pass http://b.com;
}

X-Forwarded-Proto

概念

X-Forwarded-Proto 是一个标准的请求头, 用于 proxy_set_header, 表示客户端与代理服务器或者负载均衡服务器之间的连接所采用的传输协议(https/http)

nginx
# 用于解决Set-Cookie中samesite:none;secure导致的无法成功设置cookie的问题
location / {
  proxy_set_header X-Forwarded-Proto $scheme;
}

调试技巧

在实际开发和运维中,掌握 Nginx 的调试技巧能够快速定位和解决问题。以下是一些常用的调试方法:

开启调试日志

概念

通过配置 error_log 指令的日志级别,可以获取更详细的调试信息,帮助排查问题

nginx
# 日志级别:debug | info | notice | warn | error | crit | alert | emerg
# debug 级别会输出最详细的信息,但会影响性能,仅建议在调试时使用

http {
    # 全局错误日志
    error_log /var/log/nginx/error.log debug;
}

server {
    # 服务器级别的错误日志
    error_log /var/log/nginx/example.error.log debug;

    # 访问日志,记录每个请求的详细信息
    access_log /var/log/nginx/example.access.log combined;
}

自定义日志格式

概念

通过 log_format 指令自定义日志格式,可以记录更多有用的调试信息

nginx
http {
    # 定义详细的日志格式
    log_format debug_log '$remote_addr - $remote_user [$time_local] '
                        '"$request" $status $body_bytes_sent '
                        '"$http_referer" "$http_user_agent" '
                        'upstream: $upstream_addr '
                        'response_time: $upstream_response_time '
                        'request_time: $request_time';

    server {
        access_log /var/log/nginx/debug.log debug_log;
    }
}

使用 return 调试

概念

在配置中使用 return 指令可以快速验证请求是否匹配到预期的 location,以及变量的值是否正确

nginx
location /test {
    # 返回变量值进行调试
    return 200 "uri: $uri\nargs: $args\nrequest_uri: $request_uri";
    add_header Content-Type text/plain;
}

location ~ ^/api/(.*)$ {
    # 调试正则匹配和变量
    return 200 "matched: $1\nfull_uri: $request_uri";
    add_header Content-Type text/plain;
}

使用 add_header 调试

概念

通过 add_header 添加自定义响应头,可以在浏览器开发者工具中查看变量值和匹配情况

nginx
location /debug {
    # 添加调试信息到响应头
    add_header X-Debug-URI $uri;
    add_header X-Debug-Args $args;
    add_header X-Debug-Request-Time $request_time;
    add_header X-Debug-Upstream $upstream_addr;

    proxy_pass http://backend;
}

验证配置文件

概念

在修改配置后,使用 nginx 命令验证配置文件的语法是否正确,避免重启失败

bash
# 测试配置文件语法
nginx -t

# 测试配置文件并显示配置内容
nginx -T

# 指定配置文件进行测试
nginx -t -c /etc/nginx/nginx.conf

重载配置

概念

在验证配置正确后,使用平滑重载让新配置生效,不会中断现有连接

bash
# 平滑重载配置(推荐)
nginx -s reload

# 或使用 systemctl
systemctl reload nginx

# 重启 nginx(会中断连接)
systemctl restart nginx

查看进程和连接

概念

通过查看 Nginx 进程和连接状态,可以了解服务运行情况

bash
# 查看 nginx 进程
ps aux | grep nginx

# 查看 nginx 监听的端口
netstat -tlnp | grep nginx
# 或使用 ss 命令
ss -tlnp | grep nginx

# 查看 nginx 的连接数
netstat -n | grep :80 | wc -l

stub_status 模块

概念

stub_status 模块提供了 Nginx 的基本状态信息,可以快速了解服务器的运行状态

nginx
location /nginx_status {
    stub_status on;
    access_log off;
    # 限制访问IP
    allow 127.0.0.1;
    deny all;
}

访问 http://your-domain/nginx_status 可以看到类似以下信息:

text
Active connections: 291
server accepts handled requests
 16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106

常见问题排查

nginx
# 1. 静态资源 404 问题
location /static/ {
    root /var/www;
    # 使用 return 查看实际访问的文件路径
    # return 200 "file: /var/www$uri";
    try_files $uri $uri/ =404;
}

# 2. 代理超时问题
location /api/ {
    proxy_pass http://backend;
    # 增加超时时间进行测试
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
}

# 3. 跨域问题调试
location /api/ {
    # 添加调试头信息
    add_header X-Debug-Origin $http_origin always;
    add_header Access-Control-Allow-Origin $http_origin always;
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always;
    add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent' always;

    if ($request_method = 'OPTIONS') {
        return 204;
    }

    proxy_pass http://backend;
}

应用实践

单页应用配置

当前前端界早已被单页应用一统天下了, Nginx在单页应用中的配置, 主要是配置缓存, 负载均衡, 反向代理接口等等; 笔者就将在实际应用的配置放在这里吧

nginx

server {
    listen 80;
    server_name host.com;

    # item名字可以自定义
    # 有三种配置方式
    upstream host.com {
        # weight的值越高被派发请求的概率也就越高,可以根据服务器配置的不同来设置。
        server 127.0.0.1:8882 weight=1;
        server 127.0.0.1:8881 weight=2;
        # down表示不参与负载均衡
        server 127.0.0.1:8880 down;
    }
    upstream host.com {
        # 根据客户端IP来分配服务器,比如我第一次访问请求被派发给了192.168.101.60这台服务器,那么我之后的请求就都会发送这台服务器上,
        # 这样的话session共享的问题也就解决了
        ip_hash;
        server 127.0.0.1:8881;
        server 127.0.0.1:8880;
    }
    upstream host.com {
        # 根据添加的服务器判断哪台服务器分的连接最少就把请求给谁
        least_conn;
        server 127.0.0.1:8881
        server 127.0.0.1:8880
    }

    location / {
        index index.html;
        alias /home/www/bop/;
        try_files $uri $uri/ /index.html;
        add_header Cache-Control no-store;
        # item是在上面命名的
        proxy_pass http://host.com;
    }

    # 配置接口代理
    location ~* /api/ {
        rewrite /api/(.+) /$1  break;
        proxy_set_header X-Forwarded-For $remote_addr;
        # proxy_set_header Host  $http_host;
        proxy_pass http://host.com;
    }
    access_log /home/logs/nginx/bop.access.log monitor buffer=32k flush=5s;
    error_log /home/logs/nginx/bop.error.log;
}

多页应用配置

之前团队专门搭建了一个做活动的多页应用框架, 服务于各个活动应用开发, 其中多页应用的 Nginx 映射了各个活动的BaseUrl, 这里放个最关键的部分吧

nginx
location ~ /(.*)/detail {
    set $first_path $1;
    root /home/www/growth-upgrade/html;
    try_files $uri $uri/ $first_path/index.html;

    if (!-e $request_filename) {
        rewrite ^/(.*) /$first_path/index.html last;
        break;
    }
}


location ~ /(.*)/(.*) {
    set $first_path $1;
    set $following_path $2;

    root /home/www/growth-upgrade/html;
    try_files $uri $uri/ $first_path/index.html;

    if (!-e $request_filename) {
        rewrite ^/(.*) /$first_path/index.html last;
        break;
    }
}