ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Linux Nginx 怎么在配置文件中区分大小写和正则表达式匹配

2026/10/4 7:19:12 拓冰建站 浏览量
Linux Nginx 怎么在配置文件中区分大小写和正则表达式匹配 前言写location和server_name时到底大写小写算不算同一个以及这条规则是按前缀比还是按正则比是两个天天在用却很少有人系统梳理过的问题。典型症状都很有迷惑性location ~ \.jpg$写得好好的用户上传了一张IMG.JPG就 404或者配了location ^~ /static/却发现某个.php路径还是被正则 location 抢走了。根源在于 Nginx 有两套完全独立的匹配体系各自的大小写规则还不一样前缀匹配location /path/和location /path走的是字节级字符串比较在 Linux 上严格区分大小写。正则匹配location ~和location ~*走 PCRE 引擎用~还是~*决定是否区分大小写。而且这两套体系有明确的优先级顺序不是谁写在前面谁赢。本文把匹配顺序、大小写开关、以及$uri与$request_uri的区别讲清楚并给出一个可以直接跑起来验证的实验配置。以下基于 nginx 1.22Debian 12/ nginx 1.24RHEL 9正则引擎为 PCRE2。用nginx -V 21 | grep -o PCRE[^ ]*可以确认你的构建信息。一、location 的四类修饰符location /exact/path { } # 精确匹配exact命中即终止查找 location ^~ /static/ { } # 前缀匹配prefix命中后不再检查正则 location ~ \.php$ { } # 正则区分大小写PCRE等价于区分大小写 location ~* \.(jpg|png)$ { } # 正则不区分大小写case-insensitive location / { } # 普通前缀匹配兜底修饰符匹配方式大小写是否再看正则整串精确相等区分否命中即结束^~前缀区分否~PCRE 正则区分是按出现顺序~*PCRE 正则不区分是按出现顺序无修饰符前缀区分是按出现顺序二、匹配顺序不是谁在前谁赢Nginx 官方文档给出的算法是这样的先检查所有精确匹配命中就立刻结束。再找出最长的前缀匹配^~和普通前缀一视同仁地参与最长比较。如果这个最长前缀带^~跳过所有正则直接用这个 location。否则按配置文件中的书写顺序依次尝试正则第一个匹配成功的就用它。正则全都不匹配时回退到第 2 步记住的那个最长前缀。这里有两个反直觉的点第 3 步^~只有在它是最长前缀时才生效。如果你写了location ^~ /s/和location /static/请求/static/x的最长前缀是/static/^~那条根本不被考虑正则照样会被检查。第 4 步正则比的是顺序不是长度。location ~ \.png$写在location ~ ^/img/前面/img/a.png就归前者。三、大小写控制~、~*与 PCRE 内联开关~和~*只作用于正则本身。如果你需要更细粒度的控制例如整条正则只有一部分不区分大小写可以用 PCRE 的内联标志(?i)# 只让文件名部分不区分大小写目录部分严格区分 location ~ ^/Uploads/(?i:[a-z0-9_])\.(jpg|png)$ { add_header X-Loc uploads-mixed always; }需要区分大小写的正则例如 token 校验就用~不要用~*location ~ ^/api/v1/Token/[A-Za-z0-9]{32}$ { add_header X-Loc token-strict always; }同一套规则也适用于server_name和map# 字面量 server_name 的匹配本身不区分大小写不必也不能加 ~* server_name www.example.com; # 正则 server_name 必须显式写 ~ 或 ~*且 ~ 是区分大小写的 server_name ~^v(?ver\d)\.example\.com$; server_name ~*^cdn\d\.example\.com$; map $http_user_agent $is_mobile { default 0; ~*android|iphone|ipad 1; # map 里也是 ~ 和 ~* 两种前缀 }注意server_name有一个容易搞错的地方普通的名字非正则匹配是不区分大小写的这是 HTTP 协议层面的约定域名本身不区分大小写而location的前缀匹配区分大小写。两者规则不同不要互相套用。四、可运行的验证实验搭一个目录和配置用响应头直观看到这条请求落到了哪个 location。mkdir -p /srv/www/static /srv/www/Img printf static\n /srv/www/static/a.txt printf upper\n /srv/www/Img.PNG printf lower\n /srv/www/img.png printf exact\n /srv/www/exact.html# /etc/nginx/conf.d/loc-demo.conf server { listen 8080; server_name _; root /srv/www; location /exact.html { add_header X-Loc exact always; } # 最长前缀命中且带 ^~正则将被跳过 location ^~ /static/ { add_header X-Loc caret-tilde-static always; } # 区分大小写的正则只匹配全大写的扩展名 location ~ \.PNG$ { add_header X-Loc regex-case-sensitive always; } # 不区分大小写的正则兜住小写和其它大小写组合 location ~* \.png$ { add_header X-Loc regex-case-insensitive always; } location / { add_header X-Loc prefix-fallback always; } }nginx -t nginx -s reload for u in /exact.html /static/a.txt /Img.PNG /img.png /other.txt; do printf %-16s - $u curl -s -o /dev/null -D - http://127.0.0.1:8080$u | awk -F: /^X-Loc/{print $2} done预期输出awk仅用于提取响应头与 Nginx 无关/exact.html - exact /static/a.txt - caret-tilde-static /Img.PNG - regex-case-sensitive /img.png - regex-case-insensitive /other.txt - prefix-fallback把^~ /static/改成location /static/去掉^~再重载然后访问/static/logo.png你会看到它落到正则 location而不是/static/——这就是最长前缀没有^~正则优先的直接证据。五、$uri与$request_uri匹配用的是哪一个Nginx 的 location 匹配用的是规范化后的 URI也就是$uri百分号编码已被解码//被合并.和..已被解析。$request_uri是客户端发来的原始串不做任何处理。# 区分大小写的判断应针对 $uri且注意它是解码后的值 if ($uri ~ ^/admin) { return 403; }这带来一个真实的安全相关差异/Admin和/admin是两个不同的$uri而/a/../admin会被规范成/admin。做路径级访问控制时用location ~ ^/admin/这种基于解码后 URI 的匹配比在if里拿$request_uri做字符串比较更可靠。反过来如果你要审计用户原始请求长什么样必须用$request_uri。# 记录原始请求含编码两者都要留着排查 log_format audit req$request_uri uri$uri host$host status$status;常见坑点❌ 写location ~ \.(jpg|png|gif)$以为能覆盖IMG.JPG。 ✅~区分大小写。要兼容用户上传的各种大小写改成~*或者把大写形态一并列进字符类。❌ 用location ^~ /s/去抢/static/的请求没意识到^~只有成为最长前缀才生效。 ✅ 前缀长度不够时^~会被忽略正则该匹配照样执行。要屏蔽正则前缀必须写够长。❌ 认为正则 location 之间更具体的赢或更长的赢。 ✅ 正则按书写顺序第一个匹配即胜出。把更具体的规则写在前面。❌ 在location前缀匹配里期待域名那样的不区分大小写行为。 ✅server_name的字面量匹配不区分大小写location前缀匹配区分两者规则不同。❌ 静态资源目录用了大写开头的路径如/Images/用户输入小写就 404。 ✅ Linux 文件系统本身就区分大小写location 只是如实地基于解码后的$uri比较。要么统一命名规范要么显式用~*兜住。❌ 用alias配正则 location 却忘了捕获组。 ✅location ~ ^/img/(.)$ { alias /data/$1; }需要捕获组写成alias /data/;会得到拼接路径。能用root表达就别用alias。❌ 用if ($request_uri ~ ^/api)做权限判断被/API、/a/../api之类的变形绕过。 ✅ 用基于$uri已规范化解码的location匹配或先统一做大小写归一再判断。❌ 大量正则 location 让长 URI 的匹配变慢却以为是后端慢。 ✅ location 匹配在每个请求上都要跑一遍。静态路径优先用前缀或正则只留给真正需要模糊匹配的少数路径。总结写法匹配类型大小写优先级规则location /x精确区分最高命中即结束location ^~ /x/前缀区分作为最长前缀命中时跳过正则location ~ re正则区分按书写顺序先到先得location ~* re正则不区分按书写顺序先到先得location /x/前缀区分最低作为兜底server_name www.a.com字面量不区分精确域名优先server_name ~^re/~*^re正则由~/~*决定字面量匹配失败后再试区分大小写的关键在于两处一是正则用~还是~*需要细粒度控制时用 PCRE 的(?i)二是明白前缀匹配在 Linux 上就是字节比较、不区分大小写这件事并不存在。至于是不是正则看 location 有没有~/~*前缀即可但要记住真正决定胜负的是精确 最长^~前缀 顺序正则 最长普通前缀这条优先级链而不是配置文件的书写位置。