Nginx 反向代理总 404?Location 匹配与 proxy_pass 尾斜杠避坑手册

配反向代理最坑的不是 proxy_pass 写错,而是写了却不知道自己踩中哪条规则 :location /api/ 配了 proxy_pass http://后端:9090/,后端收到的却是 /user/list,路径前缀凭空消失;location ^~ /static/ 明明写了,图片请求还是被后面的正则拦走;proxy_pass 后面一个 / 的差别,能让 404 和 200 同时出现在同一份配置里。本文从 Nginx 部署与配置文件的五层结构讲起,把虚拟主机、HTTPS、Basic Auth、动静分离与 PHP 站点补齐,最后用一整章拆透 location 的匹配顺序与 proxy_pass 的路径重构规则,再跑一遍代理本地与代理远端的三类匹配实战。

全文围绕一个问题:请求进来之后,到底命中了哪条 location,转发出去的 URL 长什么样。文中命令名与选项均标注英文原意,完整清单见文末《附:命令英文全称速查表》。

本文概要:

项目 内容
文章主题 Nginx 建站主线:部署 → 配置五层结构 → 虚拟主机 / HTTPS / 认证 → 动静分离与 PHP → 反向代理与 Location 匹配
所属类别 Linux 系统管理 → Web 服务 → Nginx
知识关键字 nginx.conf · include · server_name · listen · root · location · proxy_pass · upstream · ssl_certificate · auth_basic · php-fpm · rewrite

概念图给出配置文件的五层嵌套关系,本文章节与它一一对应:部署在前两章;虚拟主机、HTTPS 与 Basic Auth 在第三到五章;动静分离与 PHP 在第六章;反向代理与 location 匹配在最后两章。

本文的机器与地址约定 :客户端 zhb-m2(10.1.8.12);代理服务器 zhb-s2(10.1.8.102,域名 www.zhb.cloud);三台后端 Web 服务器 zhb-s3 / zhb-s4 / zhb-s5(10.1.8.103 / .104 / .105)。本文所有域名都靠 /etc/hosts 本地解析,不涉及真实备案域名。

一、部署 Nginx:四步起站

1.1 四步总览

步骤 做什么 用哪条命令
1 装包 yum -y install nginx
2 启用并启动服务 systemctl enable nginx --now
3 准备主页 echo ... > /usr/share/nginx/html/index.html
4 放行 HTTP 并验证 firewall-cmd --add-service=http --permanent

命令的写法与参数见 1.2,验证方式见 1.3。

1.2 装、起、备、放行

bash 复制代码
# 安装 nginx
[root@zhb-s2 ~]# yum -y install nginx

# 启用并启动服务(--now 等价于 enable + start)
[root@zhb-s2 ~]# systemctl enable nginx --now

# 把默认主页挪走,换成自己的
[root@zhb-s2 ~]# mv /usr/share/nginx/html/index.html{,.ori}
[root@zhb-s2 ~]# echo Hello World From Nginx > /usr/share/nginx/html/index.html

# 放行 HTTP:先写永久规则,再重载让它当场生效
[root@zhb-s2 ~]# firewall-cmd --add-service=http --permanent
[root@zhb-s2 ~]# firewall-cmd --reload

1.3 客户端验证

域名解析用 hosts 文件,不改 DNS:

系统 hosts 文件路径
Windows C:\Windows\System32\drivers\etc\hosts
Linux / Unix /etc/hosts
bash 复制代码
# 在客户端 hosts 里加一条记录,把域名指到 Nginx 服务器
10.1.8.102 www.zhb.cloud

# 验证
[root@zhb-m2 ~]# curl http://www.zhb.cloud/
Hello World From Nginx

二、配置文件的五层结构

2.1 五层嵌套关系

Nginx 配置是层级化、模块化的嵌套结构,整体是「全局块 → 核心模块块 → 业务模块块」:

层 配置块 管什么 能嵌套什么
1 全局块 整个 Nginx 进程的基础参数,不嵌套在任何块内 ------
2 events 块 网络连接相关配置 ------
3 http 块 HTTP/HTTPS 服务的公共配置 多个 server
4 server 块 虚拟主机(域名 / 端口) 多个 location
5 location 块 URL 路径匹配规则 更多 location
nginx 复制代码
# 第 1 层:全局配置
user nginx;                              # 运行 Nginx 的用户 / 用户组
worker_processes auto;                   # 工作进程数,auto = 按 CPU 核心数
error_log /var/log/nginx/error.log;      # 错误日志路径
pid /run/nginx.pid;                      # 主进程 PID 文件
include /usr/share/nginx/modules/*.conf; # 加载外部模块配置

# 第 2 层:核心模块
events {
    worker_connections 1024;  # 每个工作进程的最大并发连接数
    use epoll;                # 事件驱动模型,epoll 是 Linux 下的高性能选择
    multi_accept on;          # 允许一个进程一次接受多个新连接
}

# 第 3 层:业务模块
http {
    include       /etc/nginx/mime.types;         # MIME 类型映射
    default_type  application/octet-stream;      # 默认响应类型
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request"';
    access_log  /var/log/nginx/access.log  main; # 访问日志
    sendfile     on;                             # 高效文件传输
    keepalive_timeout  65;                       # 长连接超时时间

    # 第 4 层:虚拟主机
    server {
        listen       80;
        server_name  localhost;
        root         /usr/share/nginx/html;

        # 第 5 层:路径匹配
        location / {
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
        }

        error_page  404              /404.html;
        error_page  500 502 503 504  /50x.html;
    }
}

2.2 配置加载机制

机制 说明
include 指令 引入外部配置文件,实现模块化管理。常见做法是把虚拟主机拆到 /etc/nginx/conf.d/*.conf、代理配置拆到 /etc/nginx/default.d/*.conf
同层级优先级 后定义的覆盖先定义的
跨层级优先级 子级(location)覆盖父级(server / http)
location 匹配 精确匹配 = > 前缀匹配 ^~ > 正则匹配 ~ / ~* > 普通前缀(详见第七章)

2.3 nginx.conf 逐行详解

下面这份是 CentOS 7 默认安装的 /etc/nginx/nginx.conf,逐行加了注释,第一次读配置就从它入手。

ini 复制代码
# 更多配置详情参考官方文档:
#   * 英文官方文档: http://nginx.org/en/docs/
#   * 俄文官方文档: http://nginx.org/ru/docs/

# 指定 Nginx 工作进程的运行用户
user nginx;

# 工作进程数,auto 表示按 CPU 核心数自动调整
worker_processes auto;

# 错误日志路径
error_log /var/log/nginx/error.log;

# 主进程 PID 文件路径
pid /run/nginx.pid;

# 加载动态模块(详见 /usr/share/doc/nginx/README.dynamic)
include /usr/share/nginx/modules/*.conf;

# 事件模块:设置网络连接相关参数
events {
    # 每个工作进程的最大并发连接数,默认 1024
    worker_connections 1024;
}

# HTTP 核心模块:HTTP 服务的主要配置
http {
    # 定义名为 main 的访问日志格式
    # 字段依次为:客户端IP - 远程用户 [访问时间] "请求信息" 状态码 发送字节数 "来源页面" "用户代理" "代理IP"
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    # 启用访问日志
    access_log  /var/log/nginx/access.log  main;

    # 开启高效文件传输模式,减少磁盘 I/O 与 CPU 消耗
    sendfile            on;

    # 累积数据后一次性发送,提高网络效率(需配合 sendfile)
    tcp_nopush          on;

    # 禁用 Nagle 算法,减少传输延迟(适合实时性要求高的场景)
    tcp_nodelay         on;

    # 长连接超时时间,超过 65 秒无活动则关闭
    keepalive_timeout   65;

    # 文件类型哈希表容量,增大可提高查找效率
    types_hash_max_size 4096;

    # 引入 MIME 类型配置,定义后缀与响应类型的对应关系
    include             /etc/nginx/mime.types;

    # 默认 MIME 类型,识别不出时按二进制流返回
    default_type        application/octet-stream;

    # 加载 conf.d 下所有 .conf,这就是「虚拟主机拆文件」的入口
    # 更多说明参考 nginx.org/en/docs/ngx_core_module.html#include
    include /etc/nginx/conf.d/*.conf;

    # 默认虚拟主机
    server {
        # 监听 IPv4 / IPv6 的 80 端口
        listen       80;
        listen       [::]:80;

        # _ 表示匹配所有未明确指定的域名
        server_name  _;

        # 网站根目录
        root         /usr/share/nginx/html;

        # 加载默认虚拟主机的额外配置
        include /etc/nginx/default.d/*.conf;

        # 404 错误页
        error_page 404 /404.html;
        location = /404.html {
        }

        # 5xx 服务器错误页
        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        }
    }

    # HTTPS 服务配置,默认整段注释,启用时取消注释并配好证书
    # server {
    #     listen       443 ssl http2;
    #     listen       [::]:443 ssl http2;
    #     server_name  _;
    #     root         /usr/share/nginx/html;
    #
    #     ssl_certificate     "/etc/pki/nginx/server.crt";   # 公钥
    #     ssl_certificate_key "/etc/pki/nginx/private/server.key";  # 私钥
    #     ssl_session_cache   shared:SSL:1m;
    #     ssl_session_timeout 10m;
    #     ssl_ciphers HIGH:!aNULL:!MD5;
    #     ssl_prefer_server_ciphers on;
    #
    #     include /etc/nginx/default.d/*.conf;
    #     error_page 404 /404.html;
    #     error_page 500 502 503 504 /50x.html;
    # }
}

⚠️ 注意 :改完配置先 nginx -t 验证语法,通过再 systemctl restart nginx。配置写错会让整个站点的所有虚拟主机一起 502,而不是只影响你改的那一个。

三、虚拟主机:同一台机器跑多个站点

虚拟主机 = 同一个 Web 服务器对外提供多个站点。区分方式有三种:主机名 、端口号、IP 地址(基本不用)。

3.1 按域名区分

bash 复制代码
# 参照主配置文件里的 server 块写一份独立配置
[root@zhb-s2 ~]# cp /etc/nginx/nginx.conf /etc/nginx/conf.d/vhost-name.conf
[root@zhb-s2 ~]# vim /etc/nginx/conf.d/vhost-name.conf
nginx 复制代码
server {
    server_name  web1.zhb.cloud;
    root         /usr/share/nginx/web1;
}
server {
    server_name  web2.zhb.cloud;
    root         /usr/share/nginx/web2;
}
bash 复制代码
# 建目录、放页面、重启
[root@zhb-s2 ~]# mkdir /usr/share/nginx/web{1,2}
[root@zhb-s2 ~]# echo web1.zhb.cloud > /usr/share/nginx/web1/index.html
[root@zhb-s2 ~]# echo web2.zhb.cloud > /usr/share/nginx/web2/index.html
[root@zhb-s2 ~]# systemctl restart nginx
bash 复制代码
# 客户端加两条解析后分别访问
10.1.8.102 web1.zhb.cloud
10.1.8.102 web2.zhb.cloud

[root@zhb-m2 ~]# curl http://web1.zhb.cloud/
web1.zhb.cloud
[root@zhb-m2 ~]# curl http://web2.zhb.cloud/
web2.zhb.cloud

做完实验记得把配置文件挪走,免得干扰后续实验:

bash 复制代码
[root@zhb-s2 ~]# mkdir /etc/nginx/conf.d/vhosts
[root@zhb-s2 ~]# mv /etc/nginx/conf.d/vhost-name.conf /etc/nginx/conf.d/vhosts

3.2 按端口区分

bash 复制代码
[root@zhb-s2 ~]# vim /etc/nginx/conf.d/vhost-port.conf
nginx 复制代码
server {
    listen       8081;
    server_name  www.zhb.cloud;
    root         /usr/share/nginx/8081;
}
server {
    listen       8082;
    server_name  www.zhb.cloud;
    root         /usr/share/nginx/8082;
}
bash 复制代码
[root@zhb-s2 ~]# mkdir /usr/share/nginx/808{1,2}
[root@zhb-s2 ~]# echo 8081 > /usr/share/nginx/8081/index.html
[root@zhb-s2 ~]# echo 8082 > /usr/share/nginx/8082/index.html
[root@zhb-s2 ~]# systemctl restart nginx

# 客户端按端口访问
[root@zhb-m2 ~]# curl http://www.zhb.cloud:8081
8081
[root@zhb-m2 ~]# curl http://www.zhb.cloud:8082
8082

3.3 三种方式对比

区分方式 靠哪个指令 优点 典型场景
主机名 server_name 一个 80 端口跑多个站点,对外最干净 多域名托管
端口号 listen 不依赖域名解析,调试最省事 内部测试、灰度发布
IP 地址 listen <IP>:80 ------ 多网卡且不能改域名时才会用

四、给站点套上 HTTPS

4.1 三步生成自签证书

bash 复制代码
# 第一步:生成私钥
[root@zhb-s2 ~]# mkdir certs && cd certs
[root@zhb-s2 certs]# openssl genrsa -out www.key 2048

# 第二步:生成证书请求文件 csr(CN 的值必须是网站域名)
[root@zhb-s2 certs]# openssl req -new -key www.key -out www.csr \
  -subj "/C=CN/ST=JS/L=NJ/O=ZHB/OU=DEVOPS/CN=www.zhb.cloud/emailAddress=webadmin@zhb.cloud"

# 第三步:用自己的私钥给请求文件签名,生成证书 crt
[root@zhb-s2 certs]# openssl x509 -req -days 3650 -in www.csr -signkey www.key -out www.crt

4.2 配置站点与 301 跳转

bash 复制代码
# 把证书挪到统一目录
[root@zhb-s2 certs]# mkdir /etc/ssl/certs/www.zhb.cloud
[root@zhb-s2 certs]# mv www* /etc/ssl/certs/www.zhb.cloud

# 参照默认配置改一份 HTTPS 站点
[root@zhb-s2 ~]# cp /etc/nginx/nginx.conf /etc/nginx/conf.d/vhost-www.zhb.cloud-ssl.conf
[root@zhb-s2 ~]# vim /etc/nginx/conf.d/vhost-www.zhb.cloud-ssl.conf
nginx 复制代码
server {
    listen       443 ssl http2;
    listen       [::]:443 ssl http2;
    server_name  www.zhb.cloud;
    root         /usr/share/nginx/html;
    # 公钥
    ssl_certificate     "/etc/ssl/certs/www.zhb.cloud/www.crt";
    # 私钥
    ssl_certificate_key "/etc/ssl/certs/www.zhb.cloud/www.key";
}

# 把 80 端口的访问整体 301 到 HTTPS
server {
    listen       80;
    listen       [::]:80;
    server_name  www.zhb.cloud;
    root         /usr/share/nginx/html;
    return       301 https://$host$request_uri;
}
bash 复制代码
[root@zhb-s2 ~]# systemctl restart nginx

# 防火墙放行 HTTPS
[root@zhb-s2 ~]# firewall-cmd --add-service=https --permanent
[root@zhb-s2 ~]# firewall-cmd --reload

4.3 客户端验证

bash 复制代码
# HTTP 访问会拿到 301,被重定向到 HTTPS
[root@zhb-m2 ~]# curl http://www.zhb.cloud/
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.20.1</center>
</body>
</html>

# -k:忽略自签证书的校验(目标站点不是受信任的 CA 签发的)
[root@zhb-m2 ~]# curl -k https://www.zhb.cloud/

# -L:跟随 301 跳转,一条命令走完 HTTP → HTTPS
[root@zhb-m2 ~]# curl -Lk http://www.zhb.cloud/

五、Basic Auth 基本认证

Basic Auth 的用户名和密码以 base64 明文传输,必须配 SSL/TLS 才有意义。

bash 复制代码
# 装生成密码文件的工具
[root@zhb-s2 ~]# yum -y install httpd-tools

# 在需要保护的 server 块里加一段 location
[root@zhb-s2 ~]# vim /etc/nginx/conf.d/vhost-www.zhb.cloud-ssl.conf
nginx 复制代码
server {
    # ... 其他配置保持不变
    location /auth-basic/ {
        auth_basic            "Basic Auth";     # 弹窗上显示的提示文字
        auth_basic_user_file  "/etc/nginx/.htpasswd";  # 账号密码文件
    }
}
bash 复制代码
[root@zhb-s2 ~]# systemctl restart nginx

# 建账号:-b 表示密码直接跟在命令里,-c 表示创建(会覆盖原文件)
[root@zhb-s2 ~]# htpasswd -b -c /etc/nginx/.htpasswd zhb 123456

# 准备一个测试页面
[root@zhb-s2 ~]# mkdir /usr/share/nginx/html/auth-basic
[root@zhb-s2 ~]# vim /usr/share/nginx/html/auth-basic/index.html
html 复制代码
<html>
<body>
<div style="width: 100%; font-size: 40px; font-weight: bold;">
Test Page for Basic Authentication
</div>
</body>
</html>
bash 复制代码
# 客户端带账号密码访问,-u 指定用户名和密码
[root@zhb-m2 ~]# curl -ku zhb:123456 https://www.zhb.cloud/auth-basic/

提示 :htpasswd -c 会新建并覆盖 整个密码文件。追加账号时要把 -c 去掉,否则前面建的账号会一起消失。

六、静态站点、动态站点与 PHP

6.1 概念与请求链路

静态站点:页面写死,服务器只原样返回 HTML,不做数据交互。
#mermaid-svg-jhB8de5xQZFLq868{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jhB8de5xQZFLq868 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jhB8de5xQZFLq868 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jhB8de5xQZFLq868 .error-icon{fill:#552222;}#mermaid-svg-jhB8de5xQZFLq868 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jhB8de5xQZFLq868 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jhB8de5xQZFLq868 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jhB8de5xQZFLq868 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jhB8de5xQZFLq868 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jhB8de5xQZFLq868 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jhB8de5xQZFLq868 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jhB8de5xQZFLq868 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jhB8de5xQZFLq868 .marker.cross{stroke:#333333;}#mermaid-svg-jhB8de5xQZFLq868 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jhB8de5xQZFLq868 p{margin:0;}#mermaid-svg-jhB8de5xQZFLq868 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jhB8de5xQZFLq868 .cluster-label text{fill:#333;}#mermaid-svg-jhB8de5xQZFLq868 .cluster-label span{color:#333;}#mermaid-svg-jhB8de5xQZFLq868 .cluster-label span p{background-color:transparent;}#mermaid-svg-jhB8de5xQZFLq868 .label text,#mermaid-svg-jhB8de5xQZFLq868 span{fill:#333;color:#333;}#mermaid-svg-jhB8de5xQZFLq868 .node rect,#mermaid-svg-jhB8de5xQZFLq868 .node circle,#mermaid-svg-jhB8de5xQZFLq868 .node ellipse,#mermaid-svg-jhB8de5xQZFLq868 .node polygon,#mermaid-svg-jhB8de5xQZFLq868 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jhB8de5xQZFLq868 .rough-node .label text,#mermaid-svg-jhB8de5xQZFLq868 .node .label text,#mermaid-svg-jhB8de5xQZFLq868 .image-shape .label,#mermaid-svg-jhB8de5xQZFLq868 .icon-shape .label{text-anchor:middle;}#mermaid-svg-jhB8de5xQZFLq868 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jhB8de5xQZFLq868 .rough-node .label,#mermaid-svg-jhB8de5xQZFLq868 .node .label,#mermaid-svg-jhB8de5xQZFLq868 .image-shape .label,#mermaid-svg-jhB8de5xQZFLq868 .icon-shape .label{text-align:center;}#mermaid-svg-jhB8de5xQZFLq868 .node.clickable{cursor:pointer;}#mermaid-svg-jhB8de5xQZFLq868 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jhB8de5xQZFLq868 .arrowheadPath{fill:#333333;}#mermaid-svg-jhB8de5xQZFLq868 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jhB8de5xQZFLq868 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jhB8de5xQZFLq868 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jhB8de5xQZFLq868 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jhB8de5xQZFLq868 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jhB8de5xQZFLq868 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jhB8de5xQZFLq868 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jhB8de5xQZFLq868 .cluster text{fill:#333;}#mermaid-svg-jhB8de5xQZFLq868 .cluster span{color:#333;}#mermaid-svg-jhB8de5xQZFLq868 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jhB8de5xQZFLq868 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jhB8de5xQZFLq868 rect.text{fill:none;stroke-width:0;}#mermaid-svg-jhB8de5xQZFLq868 .icon-shape,#mermaid-svg-jhB8de5xQZFLq868 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jhB8de5xQZFLq868 .icon-shape p,#mermaid-svg-jhB8de5xQZFLq868 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jhB8de5xQZFLq868 .icon-shape .label rect,#mermaid-svg-jhB8de5xQZFLq868 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jhB8de5xQZFLq868 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jhB8de5xQZFLq868 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jhB8de5xQZFLq868 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 浏览器
Web服务器
网页文件

动态站点:后端程序实时生成页面,可读写数据库、支持用户交互。
#mermaid-svg-29L5oqquOQhV1giD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-29L5oqquOQhV1giD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-29L5oqquOQhV1giD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-29L5oqquOQhV1giD .error-icon{fill:#552222;}#mermaid-svg-29L5oqquOQhV1giD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-29L5oqquOQhV1giD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-29L5oqquOQhV1giD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-29L5oqquOQhV1giD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-29L5oqquOQhV1giD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-29L5oqquOQhV1giD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-29L5oqquOQhV1giD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-29L5oqquOQhV1giD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-29L5oqquOQhV1giD .marker.cross{stroke:#333333;}#mermaid-svg-29L5oqquOQhV1giD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-29L5oqquOQhV1giD p{margin:0;}#mermaid-svg-29L5oqquOQhV1giD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-29L5oqquOQhV1giD .cluster-label text{fill:#333;}#mermaid-svg-29L5oqquOQhV1giD .cluster-label span{color:#333;}#mermaid-svg-29L5oqquOQhV1giD .cluster-label span p{background-color:transparent;}#mermaid-svg-29L5oqquOQhV1giD .label text,#mermaid-svg-29L5oqquOQhV1giD span{fill:#333;color:#333;}#mermaid-svg-29L5oqquOQhV1giD .node rect,#mermaid-svg-29L5oqquOQhV1giD .node circle,#mermaid-svg-29L5oqquOQhV1giD .node ellipse,#mermaid-svg-29L5oqquOQhV1giD .node polygon,#mermaid-svg-29L5oqquOQhV1giD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-29L5oqquOQhV1giD .rough-node .label text,#mermaid-svg-29L5oqquOQhV1giD .node .label text,#mermaid-svg-29L5oqquOQhV1giD .image-shape .label,#mermaid-svg-29L5oqquOQhV1giD .icon-shape .label{text-anchor:middle;}#mermaid-svg-29L5oqquOQhV1giD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-29L5oqquOQhV1giD .rough-node .label,#mermaid-svg-29L5oqquOQhV1giD .node .label,#mermaid-svg-29L5oqquOQhV1giD .image-shape .label,#mermaid-svg-29L5oqquOQhV1giD .icon-shape .label{text-align:center;}#mermaid-svg-29L5oqquOQhV1giD .node.clickable{cursor:pointer;}#mermaid-svg-29L5oqquOQhV1giD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-29L5oqquOQhV1giD .arrowheadPath{fill:#333333;}#mermaid-svg-29L5oqquOQhV1giD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-29L5oqquOQhV1giD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-29L5oqquOQhV1giD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-29L5oqquOQhV1giD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-29L5oqquOQhV1giD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-29L5oqquOQhV1giD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-29L5oqquOQhV1giD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-29L5oqquOQhV1giD .cluster text{fill:#333;}#mermaid-svg-29L5oqquOQhV1giD .cluster span{color:#333;}#mermaid-svg-29L5oqquOQhV1giD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-29L5oqquOQhV1giD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-29L5oqquOQhV1giD rect.text{fill:none;stroke-width:0;}#mermaid-svg-29L5oqquOQhV1giD .icon-shape,#mermaid-svg-29L5oqquOQhV1giD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-29L5oqquOQhV1giD .icon-shape p,#mermaid-svg-29L5oqquOQhV1giD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-29L5oqquOQhV1giD .icon-shape .label rect,#mermaid-svg-29L5oqquOQhV1giD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-29L5oqquOQhV1giD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-29L5oqquOQhV1giD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-29L5oqquOQhV1giD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 浏览器
Web服务器
后端应用服务
后端数据:本地文件、数据库等

运行原理:

  • 静态:提前把 .html 放在服务器上,用户访问时服务器原样把文件发回浏览器,没有后端程序、不查库,内容固定不变。典型如官网介绍页、企业落地页。
  • 动态:请求交给后端应用程序 ,后端从 MySQL 等数据库取数据、实时拼接生成 HTML 再返回。每次刷新都重新生成。典型如商城、博客、后台管理。

6.2 六维对照

对比项 静态站点 动态站点
内容 所有用户看到的页面一模一样,改内容必须改源码文件 不同用户 / 不同时间内容不同(个人中心、实时库存)
修改内容 改源码 → 重新上传服务器 后台在线编辑,自动存库,不用改代码
交互能力 只能靠前端 JS 做简单动画,无法提交表单存数据 支持注册登录、留言、下单、搜索、增删改查
服务器依赖 只需 Nginx / Apache,不需要语言环境和数据库 Web 服务器 + 运行环境(PHP / JDK / Python)+ 数据库,三件套缺一不可
性能与成本 速度快、资源占用极低、CDN 加速友好、服务器便宜、SEO 友好 需要运算与查库,并发高时吃资源,成本更高
常见技术栈 HTML + CSS + JS、Hugo / VitePress / Hexo PHP + MySQL + Nginx(LAMP / LNMP)、SpringBoot + MySQL、Django / Flask、Express / NestJS

6.3 PHP 站点:请求怎么走

客户端访问 PHP 网页的完整流程:

  1. 客户端向 Web 服务器请求 PHP 页面;
  2. Web 服务器把请求交给 php-fpm 处理;
  3. php-fpm 把 PHP 代码交给 PHP 程序解析执行,结果返回给 php-fpm;
  4. php-fpm 把解析结果返回给 Web 服务器;
  5. Web 服务器把结果返回给客户端。
bash 复制代码
# 装 PHP 与 php-fpm
# php-fpm:接收 web 程序发来的 PHP 代码;php:解析并执行代码
[root@zhb-s2 ~]# yum install -y php php-fpm

# 启用并启动 php-fpm
[root@zhb-s2 ~]# systemctl enable php-fpm --now

# 常见扩展包一起装
[root@zhb-s2 ~]# yum install -y php-gd php-common php-pear php-mbstring php-mcrypt

# 看版本
[root@zhb-s2 ~]# php -v

# 先在本机验证 PHP 能不能跑
[root@zhb-s2 ~]# echo "<?php echo 'PHP Test Page'.\"\n\"; ?>" > php_test.php
[root@zhb-s2 ~]# php php_test.php
PHP Test Page

# 准备一个探测页
[root@zhb-s2 ~]# echo "<?php phpinfo(); ?>" > /usr/share/nginx/html/info.php

让虚拟主机支持 PHP,核心是「所有 .php 结尾的请求转发给本机 9000 端口的 php-fpm」:

nginx 复制代码
server {
    listen       80;
    listen       [::]:80;
    server_name  www.zhb.cloud;
    root         /usr/share/nginx/html;

    # 匹配所有以 .php 结尾的请求
    location ~ \.php$ {
        # 先确认文件真实存在,不存在直接 404
        # 作用:防止 /xxx.php/yyy.jpg 这类伪造路径被 php-fpm 解析,是重要的安全防护
        try_files $uri =404;

        # 把 PHP 请求转发到本机 9000 端口的 php-fpm
        fastcgi_pass 127.0.0.1:9000;

        # 目录请求默认使用 index.php
        fastcgi_index index.php;

        # 指定要执行的 PHP 文件绝对路径
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        # 引入 Nginx 默认的 FastCGI 参数文件(含 QUERY_STRING、REQUEST_METHOD 等必需变量)
        include fastcgi_params;
    }
}

PHP 配置也可以从虚拟主机里拆出去,便于多个站点共用:

nginx 复制代码
# /etc/nginx/conf.d/vhost-www.conf 里只留一行引用
server {
    listen       80;
    listen       [::]:80;
    server_name  www.zhb.cloud;
    root         /usr/share/nginx/html;
    include /etc/nginx/default.d/*.conf;
}
nginx 复制代码
# /etc/nginx/default.d/php.conf 里放 PHP 转发规则
location ~ \.php$ {
    try_files $uri =404;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}
bash 复制代码
# 配置改完务必重启
[root@zhb-s2 ~]# systemctl restart nginx

# 客户端测试
[root@zhb-m2 ~]# curl http://www.zhb.cloud/info.php

⚠️ 注意 :location ~ \.php$ 里那行 try_files $uri =404; 不能省。PHP-FPM 有个历史漏洞:请求 /uploads/evil.jpg/1.php 这类路径时,只要路径里出现 .php 就可能被当成脚本执行------try_files 先用文件是否存在挡一道。

七、反向代理:Location 匹配与转发

7.1 反向代理做什么

反向代理指的是代理外网用户的请求到内部指定的服务器,并把数据返回给用户。客户端不直接和后端服务器通信,而是先与反向代理服务器通信,后端 IP 因此被隐藏起来。

主要作用有四个:

作用 说明
负载均衡 把请求分发给多个后端服务器,平衡负载,提升性能与可靠性
缓存 缓存静态文件或动态页面,减轻后端压力,加快响应
动静分离 动态内容(PHP / Python / Node.js)与静态资源(HTML / CSS / JS / 图片 / 视频)分别放在不同服务器或路径
多站点代理 代理多个域名或虚拟主机,把不同请求转到不同后端,实现共享端口

7.2 四个代理模块

模块 转发的协议 用途 官方文档路径
ngx_http_proxy_module HTTP 把客户端请求以 HTTP 协议转发到指定服务器 nginx.org/en/docs/http/ngx_http_proxy_module.html
ngx_http_upstream_module ------ 定义后端服务器分组,供 proxy_pass、fastcgi_pass、uwsgi_pass 引用 nginx.org/en/docs/http/ngx_http_upstream_module.html
ngx_stream_proxy_module TCP 把客户端请求以 TCP 协议转发到指定服务器 nginx.org/en/docs/stream/ngx_stream_proxy_module.html
ngx_http_fastcgi_module FastCGI 把 PHP 请求转发到指定服务器(第六章的 PHP 站点就用它) nginx.org/en/docs/http/ngx_http_fastcgi_module.html
ngx_http_uwsgi_module uwsgi 把 Python 请求转发到指定服务器 nginx.org/en/docs/http/ngx_http_uwsgi_module.html

7.3 Location 五种修饰符与优先级

反向代理的匹配本质是「URL 路径匹配 → 命中对应规则 → 按规则里的 proxy_pass 转发 」,其中 location 定义匹配路径、proxy_pass 指定后端地址。

修饰符 写法 匹配逻辑 优先级
= location = /login {} URI 与该路径完全一致才命中,命中即停止搜索 最高
^~ location ^~ /static/ {} 以该路径开头即命中;若它是最长前缀,跳过正则检查 次高
~ location ~ \.php$ {} 按正则匹配,区分大小写;多个正则按配置顺序,第一个命中即生效 中
~* `location ~* .(jpg png)$ {}` 按正则匹配,不区分大小写
无符号 location /api/ {} 普通前缀匹配,多个规则按路径最长优先 兜底
/ location / {} 所有未命中请求的最终归属 最低
nginx 复制代码
# 1. 精确匹配:只有 /login 命中,/login?a=1 与 /login/ 都不走这里
location = /login {
    proxy_pass http://backend_login:8080;
}

# 2. 前缀匹配:/static 开头全部命中,且跳过正则
location ^~ /static/ {
    proxy_pass http://backend_static:80;
}

# 3. 正则匹配:按配置顺序匹配,第一个命中即生效
location ~* \.(jpg|png|gif)$ {
    proxy_pass http://backend_img:80;
}

# 4. 普通前缀:/api/ 开头命中,路径更长的规则优先
location /api/ {
    proxy_pass http://backend_api:9090;
}
# 规则2:路径更长,/api/user/xxx 会命中这条而不是上面那条
location /api/user/ {
    proxy_pass http://backend_user:9090;
}

⚠️ 注意 :官方文档给的顺序与传统说法略有出入,准确表述是------先记住最长的前缀匹配,再按配置顺序试正则、第一个命中就用它 ;只有正则全不命中,才回退到之前记住的那个最长前缀。^~ 的作用就是「如果它是最长前缀,跳过正则检查」。所以「^~ 一定高于正则」成立的前提是它确实是最长前缀。

7.4 proxy_pass 末尾斜杠的差别

proxy_pass 末尾带不带 /,直接决定转发到后端的路径要不要保留 location 匹配到的那段前缀------这是 404 的最高频成因。

场景 配置 客户端请求 后端实际收到
末尾带 / location /api/ { proxy_pass http://10.1.8.103:9090/; } /api/user/list /user/list(/api/ 被剔除)
末尾不带 / location /api/ { proxy_pass http://10.1.8.103:9090; } /api/user/list /api/user/list(原样保留)

提示 :proxy_pass 指定的是「服务器地址」而不是「URL」,两者行为不同。带 / 时 Nginx 把 location 匹配到的那段路径替换掉;不带 / 时整段原始 URI 原样拼在后面。拿不准就用 curl -v 看后端访问日志里实际收到的路径。

7.5 综合示例与匹配流程

nginx 复制代码
http {
    # 后端分组:多节点会自动轮询,实现负载均衡
    upstream backend_main   { server 10.1.8.103:8080; }
    upstream backend_api    { server 10.1.8.104:9090; }
    upstream backend_static { server 10.1.8.105:80; }
    upstream backend_login  { server 10.1.8.103:8080; }

    server {
        listen 80;
        server_name localhost;

        # 1. 精确匹配:仅 /login → 登录服务
        location = /login {
            proxy_pass http://backend_login;
            proxy_set_header Host $host;
        }

        # 2. 前缀匹配:/static/ 开头 → 静态服务,跳过正则
        location ^~ /static/ {
            proxy_pass http://backend_static;
            proxy_set_header Host $host;
        }

        # 3. 正则匹配:图片后缀 → 静态服务
        location ~* \.(jpg|png|gif)$ {
            proxy_pass http://backend_static;
            proxy_set_header Host $host;
        }

        # 4. 普通前缀:/api/ 开头 → API 服务(末尾带 /,剔除 /api/)
        location /api/ {
            proxy_pass http://backend_api/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }

        # 5. 兜底匹配:其余全部 → 主服务
        location / {
            proxy_pass http://backend_main;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            # 传递真实 IP 链路与请求协议
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

配套的匹配结果一览:

客户端请求 URL 命中的 location 转发至后端的 URL 对应后端
http://localhost/login = /login http://10.1.8.103:8080 backend_login
http://localhost/static/css/main.css ^~ /static/ http://10.1.8.105:80/css/main.css backend_static
http://localhost/img/1.jpg `~* .(jpg png gif)$`
http://localhost/api/user/info /api/ http://10.1.8.104:9090/user/info backend_api
http://localhost/index / http://10.1.8.103:8080/index backend_main

反向代理常用请求头,转发时建议一起带上,否则后端拿不到客户端真实信息:

指令 传递什么 说明
proxy_set_header Host $host; 客户端访问的域名 后端生成绝对链接时要用
proxy_set_header X-Real-IP $remote_addr; 客户端真实 IP 后端记日志 / 限流的基础
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 逐级 IP 链路 多级代理时保留完整来源链
proxy_set_header X-Forwarded-Proto $scheme; 原始请求协议 后端判断该生成 http 还是 https 链接

八、反向代理三机实战

8.1 实验环境

主机名 IP 地址 角色
zhb-m2 10.1.8.12 客户端(测试)
zhb-s2 10.1.8.102 代理服务器(www.zhb.cloud)
zhb-s3 10.1.8.103 后端 Web1
zhb-s4 10.1.8.104 后端 Web2
zhb-s5 10.1.8.105 后端 Web3

所有节点的 /etc/hosts 统一加一份解析:

bash 复制代码
# 所有节点
[root@zhb-s2 ~]# vim /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

############ proxy ##################
10.1.8.12  zhb-m2.zhb.cloud zhb-m2
10.1.8.102 www.zhb.cloud www
10.1.8.102 zhb-s2.zhb.cloud zhb-s2
10.1.8.103 zhb-s3.zhb.cloud zhb-s3
10.1.8.104 zhb-s4.zhb.cloud zhb-s4
10.1.8.105 zhb-s5.zhb.cloud zhb-s5

8.2 后端与代理准备

bash 复制代码
# 三台后端都装 nginx 并启动,以 zhb-s3 为例
[root@zhb-s3 ~]# yum -y install nginx
[root@zhb-s3 ~]# systemctl enable nginx --now

# 放行 HTTP
[root@zhb-s3 ~]# firewall-cmd --add-service=http --permanent
[root@zhb-s3 ~]# firewall-cmd --reload

# 三台各写自己的标识页
[root@zhb-s3 ~]# echo Welcome to $(hostname) > /usr/share/nginx/html/index.html
[root@zhb-s4 ~]# echo Welcome to $(hostname) > /usr/share/nginx/html/index.html
[root@zhb-s5 ~]# echo Welcome to $(hostname) > /usr/share/nginx/html/index.html

# 客户端先直连三台后端确认都能访问
[root@zhb-m2 ~]# curl http://zhb-s3.zhb.cloud/
Welcome to zhb-s3.zhb.cloud
[root@zhb-m2 ~]# curl http://zhb-s4.zhb.cloud/
Welcome to zhb-s4.zhb.cloud
[root@zhb-m2 ~]# curl http://zhb-s5.zhb.cloud/
Welcome to zhb-s5.zhb.cloud
bash 复制代码
# 代理节点装 nginx 并准备一批素材
[root@zhb-s2 ~]# yum -y install nginx
[root@zhb-s2 ~]# echo Welcome to www.zhb.cloud > /usr/share/nginx/html/index.html

[root@zhb-s2 ~]# mkdir /var/nginx
[root@zhb-s2 ~]# echo "Hello, Nginx" > /var/nginx/index.html
[root@zhb-s2 ~]# echo "Hello, Zhb" > /var/nginx/test.txt
[root@zhb-s2 ~]# cp /usr/share/nginx/html/nginx-logo.png /var/nginx/
[root@zhb-s2 ~]# ls /var/nginx/
index.html  nginx-logo.png  test.txt
nginx 复制代码
# 代理节点第一版配置:只做一个本地静态站点
server {
    listen  80;
    server_name www.zhb.cloud;

    location / {
      root /var/nginx;
      index index.html;
    }
}
bash 复制代码
# 配置改动用 reload 生效,不断连接
[root@zhb-s2 ~]# nginx -s reload
bash 复制代码
# 客户端验证
[root@zhb-m2 ~]# curl http://www.zhb.cloud/
Hello, Nginx
[root@zhb-m2 ~]# curl http://www.zhb.cloud/test.txt
Hello, Zhb

访问 www.zhb.cloud/nginx-logo.png 能正常返回图片:
思考题 :如果在 /var/nginx 里放一个 test.nn 文件,通过 www.zhb.cloud/test.nn 能取到内容吗?答案是取到的是下载而不是渲染 ------因为 nn 后缀不在 mime.types 里,Nginx 不知道它该按什么类型返回,只能按 default_type(二进制流)处理,浏览器于是弹下载框。

8.3 代理本地:三类匹配复现

先在代理机上准备多套目录,用来区分「命中的是哪条规则」:

bash 复制代码
# /var/nginx 下放 nginx1、nginx2 两个子目录
[root@zhb-s2 ~]# mkdir /var/nginx/nginx{1,2}
[root@zhb-s2 ~]# echo "Hello, I'm here /var/nginx/nginx1" > /var/nginx/nginx1/index.html
[root@zhb-s2 ~]# echo "Hello, I'm here /var/nginx/nginx2" > /var/nginx/nginx2/index.html

# 平级的 /var/nginx1、/var/nginx2
[root@zhb-s2 ~]# mkdir /var/nginx{1,2}
[root@zhb-s2 ~]# echo "Hello, Nginx1" > /var/nginx1/index.html
[root@zhb-s2 ~]# echo "Hello, Nginx2" > /var/nginx2/index.html

# /var/www1、/var/www2 下各放 nginx1、nginx2,供正则与精确匹配实验用
[root@zhb-s2 ~]# for path1 in www{1..2}
do
  for path2 in nginx{1..2}
  do
    mkdir -p /var/$path1/$path2
    echo "Hello, I'm here /var/$path1/$path2" > /var/$path1/$path2/index.html
  done
done

# 核对目录结构
[root@zhb-s2 ~]# tree /var/nginx* /var/www*
/var/nginx
├── index.html
├── nginx1
│   └── index.html
├── nginx2
│   └── index.html
├── nginx-logo.png
└── test.txt
/var/nginx1
└── index.html
/var/nginx2
└── index.html
/var/www1
├── nginx1
│   └── index.html
└── nginx2
    └── index.html
/var/www2
├── nginx1
│   └── index.html
└── nginx2
    └── index.html
bash 复制代码
# 基线:默认只匹配 /
[root@zhb-m2 ~]# curl http://www.zhb.cloud/
Hello, Nginx
[root@zhb-m2 ~]# curl http://www.zhb.cloud/nginx1/
Hello, I'm here /var/nginx/nginx1
[root@zhb-m2 ~]# curl http://www.zhb.cloud/nginx2/
Hello, I'm here /var/nginx/nginx2

实践 1:无符号前缀匹配 ------location /nginx1 的 root /var 会拼成 /var/nginx1:

nginx 复制代码
server {
    listen  80;
    server_name www.zhb.cloud;

    location / {
        root /var/nginx;
        index index.html;
    }

    # 命中 /nginx1 时去 /var 下找 nginx1,完整路径就是 /var/nginx1
    # 等价于写 alias /var/nginx1;(alias 必须用绝对路径)
    location /nginx1 {
        root /var;
        index index.html;
    }
}
bash 复制代码
[root@zhb-s2 ~]# nginx -s reload

# 注意:/nginx1 后面这个 / 不能省
[root@zhb-m2 ~]# curl http://www.zhb.cloud/nginx1/
Hello, Nginx1
[root@zhb-m2 ~]# curl http://www.zhb.cloud/nginx2/
Hello, I'm here /var/nginx/nginx2

结论 :无符号前缀匹配的优先级高于默认的 /。

实践 2:加一条正则 ------/nginx1/ 被正则抢走:

nginx 复制代码
# 在实践1的配置上追加
location ~ /nginx.* {
    root /var/www1;
    index index.html;
}
bash 复制代码
[root@zhb-s2 ~]# nginx -s reload

[root@zhb-m2 ~]# curl http://www.zhb.cloud/nginx1/
Hello, I'm here /var/www1/nginx1
[root@zhb-m2 ~]# curl http://www.zhb.cloud/nginx2/
Hello, I'm here /var/www1/nginx2

结论:正则匹配的优先级高于无符号前缀匹配。

实践 3:再加一条精确匹配 ------/nginx2/index.html 又被抢回去:

nginx 复制代码
# 在实践2的配置上追加
location = /nginx2/index.html {
    root /var/www2;
    index index.html;
}
bash 复制代码
[root@zhb-s2 ~]# nginx -s reload

[root@zhb-m2 ~]# curl http://www.zhb.cloud/nginx1/
Hello, I'm here /var/www1/nginx1
[root@zhb-m2 ~]# curl http://www.zhb.cloud/nginx2/
Hello, I'm here /var/www2/nginx2

结论 :精确匹配的优先级高于正则匹配。三轮下来,优先级顺序 = > ^~ > ~ / ~* > 普通前缀 > / 被完整复现。

8.4 代理远端:路径重写

把 location 指向别的服务器,就成了真正的反向代理。

nginx 复制代码
server {
    listen  80;
    server_name www.zhb.cloud;

    location / {
        root /var/nginx;
        index index.html;
    }

    # 访问 /nginx1/ 开头,等价于直接访问 http://zhb-s3.zhb.cloud/
    # proxy_pass 末尾带 /,所以 /nginx1/ 不会拼到后端
    location /nginx1/ {
        proxy_pass http://zhb-s3.zhb.cloud/;
        index index.html;
    }
}
bash 复制代码
[root@zhb-s2 ~]# nginx -s reload

# /nginx1/ 转到了 zhb-s3,/nginx2/ 还是走本地目录
[root@zhb-m2 ~]# curl http://www.zhb.cloud/nginx1/
Welcome to zhb-s3.zhb.cloud
[root@zhb-m2 ~]# curl http://www.zhb.cloud/nginx2/
Hello, I'm here /var/nginx/nginx2

远端 + 正则 + 精确匹配一起上 ,并且用 rewrite 做路径替换:

nginx 复制代码
server {
    listen  80;
    server_name www.zhb.cloud;

    location / {
        root /var/nginx;
        index index.html;
    }

    location /nginx1/ {
        proxy_pass http://zhb-s3.zhb.cloud/;
        index index.html;
    }

    # 正则:去掉 /nginx1、/nginx2、/nginx3 前缀,再转给 zhb-s4
    location ~ /nginx[123].* {
        # ^/nginx[123](.*)$ 匹配以 /nginx1|2|3 开头的完整路径,$1 是前缀之后的部分
        # break 表示重写后不再匹配其他 rewrite 规则
        rewrite ^/nginx[123](.*)$ $1 break;
        # proxy_pass 不带 URI(末尾无 /),配合 rewrite 完成路径替换
        proxy_pass http://zhb-s4.zhb.cloud;
        index index.html;
    }

    # 精确匹配:只有 /nginx3/ 走这台
    location = /nginx3/ {
        proxy_pass http://zhb-s5.zhb.cloud/;
        index index.html;
    }
}
bash 复制代码
[root@zhb-s2 ~]# nginx -s reload

# /nginx1/ 和 /nginx2/ 被正则抢走,转给 zhb-s4
[root@zhb-m2 ~]# curl http://www.zhb.cloud/nginx1/
Welcome to zhb-s4.zhb.cloud
[root@zhb-m2 ~]# curl http://www.zhb.cloud/nginx2/
Welcome to zhb-s4.zhb.cloud
# /nginx3/ 被精确匹配抢回来,转给 zhb-s5
[root@zhb-m2 ~]# curl http://www.zhb.cloud/nginx3/
Welcome to zhb-s5.zhb.cloud

一次改动同时验证了三件事:rewrite 能改写转发路径、proxy_pass 不带尾斜杠时要靠 rewrite 补路径替换、精确匹配仍压得住正则。

附:命令英文全称速查表

命令 / 缩写 英文全称 / 原意 作用
nginx engine X 高性能 HTTP 与反向代理服务器
proxy proxy 代理;正向代理代表客户端,反向代理代表服务端
upstream upstream 上游,指后端服务器分组
location location 位置,指 URL 路径匹配块
root root 网站根目录
alias alias 别名,把匹配到的路径映射到另一个目录
rewrite rewrite 重写 URL 路径
ssl S ecure S ockets Layer 安全套接层,现由 TLS 取代
TLS T ransport L ayer Security 传输层安全协议
http2 HTTP version 2 HTTP 第二代协议
fastcgi Fast C ommon G ateway Interface 快速通用网关接口,PHP 走这条
php-fpm PHP F astCGI P rocess Manager PHP 的 FastCGI 进程管理器
mime M ultipurpose I nternet M ail Extensions 多用途互联网邮件扩展,此处指文件类型映射
epoll e vent poll Linux 下的高性能事件驱动模型
auth_basic auth entication basic HTTP 基本认证
htpasswd h ypert ext passw ord 生成 Basic 认证账号密码文件的工具

📚 参考资料(官方文档 · 中英双语)

官方文档(nginx.org)

中文版

其余手册在服务器上直接查:

bash 复制代码
nginx -h              # 命令行参数
nginx -t              # 检查配置语法
nginx -T              # 打印实际生效的完整配置
nginx -s reload       # 平滑重载配置
man 8 nginx           # 服务端手册

总结 :Nginx 的坑几乎都集中在两个地方。一是配置层级 :全局、events、http、server、location 层层嵌套,子级覆盖父级;虚拟主机拆到 conf.d 后用 include 汇入。二是匹配规则 :location 按「先记最长前缀 → 正则第一个命中即用 → 都不中再回退最长前缀」的顺序选;proxy_pass 末尾带不带 /,决定路径前缀是剔除还是保留。把这两条吃透,404 与 502 基本都能自己定位。


📚 同系列 · Linux 系统管理(其余篇目见博客主页)

blog.csdn.net/nbsxl

  • 上一篇:《Linux 趣味命令与 Nginx 云盘建站实操记录》
  • 相关篇:《Linux firewalld 防火墙区域与规则实战指南》
  • 相关篇:《Linux SELinux 安全加固与标签排错手册》

💬 你的 proxy_pass 尾斜杠踩过坑吗?把 curl -v 的请求行贴到评论区,一起看路径是怎么被改的。

相关推荐
Elastic 中国社区官方博客1 小时前
14 个 alerts,1 个 incident:使用 Elasticsearch 中的 ES|QL 衡量 alerting rule 噪声
大数据·运维·数据库·elasticsearch·搜索引擎·全文检索
Lsetea1 小时前
OpenSSL报permitted subtree violation:证书SAN与CA名称约束排查
运维·https·ssl证书·openssl·证书链
ShayneLee81 小时前
Nginx只开一个端口!能做什么?(一)
运维·nginx
万联WANFLOW2 小时前
Docker Hub 镜像拉取慢、timeout 的排查方法
运维·docker·云原生·容器·eureka
天衍四九-2 小时前
Docker Compose企业实战系列(一):LNMP环境一键部署(Nginx\+MySQL\+PHP)
mysql·nginx·docker
Julien20042 小时前
CGroups 资源控制组
linux·运维·服务器·ssh·学习方法
皓月盈江2 小时前
Linux Debian系统安装Google Chrome谷歌浏览器教程
linux·chrome·debian·谷歌浏览器
Vcaker2 小时前
Linux学习37-rook-ceph部署
linux·运维·学习
Mortalbreeze2 小时前
MySQL 基础篇(二):数据库和数据表的基本操作
linux·服务器·数据库·mysql