摘要:本文详细介绍 Nginx 中正则表达式的使用方法,涵盖 location 匹配、rewrite 重写、条件判断、变量提取和实战案例。通过本文档,你将掌握 Nginx 正则表达式的核心语法,能够灵活处理复杂的 URL 匹配和重写需求。
关键词:Nginx、正则表达式、location、rewrite、URL重写、条件判断、变量提取
适合人群:有 Nginx 基础的开发者、想深入学习 URL 匹配的工程师
阅读时间:约 40 分钟
版本信息:Nginx 1.24+ | PCRE 库支持
文章目录
- 1. 正则表达式基础
- 1.1 什么是正则表达式
- 1.2 Nginx 正则语法
- 1.3 常用正则符号
- 2. location 正则匹配
- 2.1 location 匹配优先级
- 2.2 精确匹配(=)
- 2.3 优先前缀匹配(^~)
- 2.4 正则匹配(~ 和 ~*)
- 2.5 实战:完整 location 配置
- 3. rewrite 重写规则
- 3.1 rewrite 语法
- 3.2 基础重写示例
- 3.3 正则捕获组
- 3.4 条件重写
- 3.5 实战:URL 重写案例
- 案例 1:旧域名迁移
- 案例 2:API 版本管理
- 案例 3:SEO 友好 URL
- 4. 条件判断
- 4.1 if 指令语法
- 4.2 请求方法判断
- 4.3 User-Agent 判断
- 4.4 文件存在判断
- 4.5 查询参数判断
- 5. 变量提取
- 5.1 内置变量
- 5.2 正则捕获变量
- 5.3 自定义变量
- 5.4 map 指令
- 6. 实战案例
- 实战 1:图片服务器
- 实战 2:API 网关路由
- 实战 3:多语言网站
- 实战 4:防盗链配置
- 7. 常见问题 FAQ
- 8. 学习资源与建议
- 学习建议
- 官方资源
- 推荐工具
1. 正则表达式基础
1.1 什么是正则表达式
正则表达式(Regular Expression)是一种用于匹配字符串模式的工具。在 Nginx 中,正则表达式主要用于:
- location 匹配:根据 URL 模式选择处理方式
- rewrite 重写:将 URL 重写为新的格式
- 条件判断:根据请求特征执行不同逻辑
- 变量提取:从 URL 中提取参数
1.2 Nginx 正则语法
Nginx 使用 PCRE(Perl Compatible Regular Expressions)库,支持以下匹配操作符:
| 操作符 | 说明 | 示例 |
|---|---|---|
~ | 区分大小写的正则匹配 | ~ \.php$ |
~* | 不区分大小写的正则匹配 | `~* .(jpg |
!~ | 区分大小写的正则不匹配 | !~ \.php$ |
!~* | 不区分大小写的正则不匹配 | `!~* .(jpg |
1.3 常用正则符号
Nginx 支持的正则符号:
| 符号 | 说明 | 示例 | 匹配 |
|---|---|---|---|
. | 任意单个字符 | a.c | abc, a1c, a-c |
* | 0 次或多次 | ab*c | ac, abc, abbc |
+ | 1 次或多次 | ab+c | abc, abbc |
? | 0 次或 1 次 | ab?c | ac, abc |
^ | 字符串开头 | ^abc | abc, abcdef |
$ | 字符串结尾 | abc$ | abc, 123abc |
[] | 字符集合 | [abc] | a, b, c |
[^] | 否定字符集合 | [^abc] | 除 a,b,c 外的字符 |
() | 分组 | (abc)+ | abc, abcabc |
| ` | ` | 或 | `a |
\d | 数字 | \d+ | 123, 456 |
\w | 单词字符 | \w+ | abc, 123 |
\s | 空白字符 | \s+ | 空格, 制表符 |
💡提示:Nginx 的正则表达式不需要使用
/包裹,直接使用模式即可。
2. location 正则匹配
2.1 location 匹配优先级
Nginx 的 location 匹配有严格的优先级顺序:
1. = 精确匹配(最高优先级) 2. ^~ 优先前缀匹配 3. ~ 区分大小写的正则匹配 4. ~* 不区分大小写的正则匹配 5. / 通用前缀匹配(最低优先级)匹配流程:
请求到达 → 检查精确匹配(=) → 检查优先前缀匹配(^~) → 检查正则匹配(~/~*) → 使用最长前缀匹配2.2 精确匹配(=)
精确匹配整个 URL 路径:
# 只匹配 / location = / { return 200 "首页"; } # 只匹配 /about location = /about { return 200 "关于页面"; } # 只匹配 /api/v1/users location = /api/v1/users { proxy_pass http://backend; }💡提示:精确匹配优先级最高,但只匹配完全相同的 URL。
2.3 优先前缀匹配(^~)
匹配 URL 前缀,且停止正则匹配:
# 匹配 /images/ 开头的所有路径 location ^~ /images/ { root /var/www/static; expires 30d; } # 匹配 /static/ 开头的所有路径 location ^~ /static/ { root /var/www; expires 30d; }💡提示:
^~匹配成功后,Nginx 不再检查正则匹配,适合静态资源。
2.4 正则匹配(~ 和 ~*)
使用正则表达式匹配 URL:
# 区分大小写:匹配 .php 文件 location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; } # 不区分大小写:匹配图片文件 location ~* \.(jpg|jpeg|png|gif|ico|svg)$ { root /var/www/images; expires 30d; add_header Cache-Control "public, immutable"; } # 不区分大小写:匹配 CSS/JS 文件 location ~* \.(css|js)$ { root /var/www/static; expires 7d; } # 匹配特定格式的文档 location ~* \.(pdf|doc|docx|xls|xlsx)$ { root /var/www/documents; }2.5 实战:完整 location 配置
server { listen 80; server_name example.com; # 1. 精确匹配首页 location = / { root /var/www/html; index index.html; } # 2. 优先前缀匹配:静态资源 location ^~ /static/ { root /var/www; expires 30d; } # 3. 正则匹配:图片 location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ { root /var/www/images; expires 30d; } # 4. 正则匹配:CSS/JS location ~* \.(css|js)$ { root /var/www/static; expires 7d; } # 5. 正则匹配:PHP location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; } # 6. 通用前缀匹配:API location /api { proxy_pass http://backend; } # 7. 默认匹配 location / { root /var/www/html; index index.html; try_files $uri $uri/ =404; } }3. rewrite 重写规则
3.1 rewrite 语法
rewrite指令用于重写 URL:
rewrite regex replacement [flag];参数说明:
| 参数 | 说明 |
|---|---|
regex | 正则表达式模式 |
replacement | 替换后的 URL |
flag | 标志(last, break, redirect, permanent) |
flag 说明:
| 标志 | 说明 | 使用场景 |
|---|---|---|
last | 重写后继续匹配新的 location | 内部重写 |
break | 重写后停止匹配,直接处理 | 内部重写 |
redirect | 302 临时重定向 | 临时跳转 |
permanent | 301 永久重定向 | 永久跳转 |
3.2 基础重写示例
将旧 URL 重写到新 URL:
server { listen 80; server_name example.com; # 301 永久重定向 rewrite ^/old-page$ /new-page permanent; # 302 临时重定向 rewrite ^/temp-page$ /new-page redirect; # 内部重写(不改变浏览器 URL) rewrite ^/api/v1/(.*)$ /api/v2/$1 last; }3.3 正则捕获组
使用()捕获 URL 中的部分:
server { listen 80; server_name example.com; # 捕获用户 ID rewrite ^/user/(\d+)$ /profile?id=$1 last; # 捕获年份、月份、日期 rewrite ^/blog/(\d{4})/(\d{2})/(\d{2})/(.*)$ /article?year=$1&month=$2&day=$3&slug=$4 last; # 捕获文件扩展名 rewrite ^/download/(.*)\.(zip|tar\.gz)$ /files/$1.$2 last; }捕获组说明:
$1:第一个捕获组$2:第二个捕获组- 以此类推
3.4 条件重写
使用if指令进行条件重写:
server { listen 80; server_name example.com; # 根据 User-Agent 重写 if ($http_user_agent ~* "mobile") { rewrite ^/$ /mobile.html last; } # 根据 Cookie 重写 if ($http_cookie ~* "theme=dark") { rewrite ^/$ /dark-theme.html last; } # 根据查询参数重写 if ($arg_version = "v1") { rewrite ^/api/(.*)$ /api/v1/$1 last; } }💡提示:Nginx 官方建议尽量少用
if,因为其行为在某些情况下不可预测。
3.5 实战:URL 重写案例
案例 1:旧域名迁移
server { listen 80; server_name old.example.com; # 所有请求 301 到新域名 rewrite ^(.*)$ https://new.example.com$1 permanent; }案例 2:API 版本管理
server { listen 80; server_name api.example.com; # 默认使用 v2 rewrite ^/api/(.*)$ /api/v2/$1 last; # 指定版本 location ~ ^/api/v(\d+)/ { proxy_pass http://backend; } }案例 3:SEO 友好 URL
server { listen 80; server_name blog.example.com; # /2024/01/01/my-post → /post?slug=my-post&date=2024-01-01 rewrite ^/(\d{4})/(\d{2})/(\d{2})/(.+)$ /post?slug=$4&date=$1-$2-$3 last; # /category/tech → /posts?category=tech rewrite ^/category/(.+)$ /posts?category=$1 last; # /tag/nginx → /posts?tag=nginx rewrite ^/tag/(.+)$ /posts?tag=$1 last; }4. 条件判断
4.1 if 指令语法
Nginx 的if指令用于条件判断:
if (condition) { # 配置指令 }条件类型:
| 条件类型 | 示例 | 说明 |
|---|---|---|
| 变量比较 | $request_method = POST | 变量等于某值 |
| 正则匹配 | $http_user_agent ~* "mobile" | 正则匹配 |
| 文件存在 | -f $request_filename | 文件存在 |
| 目录存在 | -d $request_filename | 目录存在 |
| 文件/目录/链接不存在 | !-e $request_filename | 不存在 |
| 可执行文件 | -x $request_filename | 可执行 |
4.2 请求方法判断
server { listen 80; server_name api.example.com; # 只允许 GET 和 POST if ($request_method !~ ^(GET|POST)$) { return 405; } # GET 请求处理 if ($request_method = GET) { # 处理 GET 请求 } # POST 请求处理 if ($request_method = POST) { # 处理 POST 请求 } }4.3 User-Agent 判断
server { listen 80; server_name example.com; # 移动端跳转 if ($http_user_agent ~* "(mobile|android|iphone|ipad)") { rewrite ^/$ /mobile.html last; } # 搜索引擎爬虫 if ($http_user_agent ~* "(googlebot|baiduspider|bingbot)") { # 允许爬虫访问 } # 禁止特定爬虫 if ($http_user_agent ~* "(badbot|evilbot)") { return 403; } }4.4 文件存在判断
server { listen 80; server_name example.com; root /var/www/html; # 如果文件不存在,返回 404 if (!-f $request_filename) { return 404; } # 如果目录不存在,创建 if (!-d $request_filename) { # 处理逻辑 } # SPA 路由支持 if (!-e $request_filename) { rewrite ^(.*)$ /index.html last; } }4.5 查询参数判断
server { listen 80; server_name example.com; # 检查特定查询参数 if ($arg_token = "") { return 401; } # 检查多个参数 if ($arg_version = "v1") { rewrite ^/api/(.*)$ /api/v1/$1 last; } # 检查参数是否存在 if ($arg_debug) { # 调试模式 } }💡提示:
$arg_参数名可以获取 URL 查询参数。
5. 变量提取
5.1 内置变量
Nginx 提供了大量内置变量:
| 变量 | 说明 | 示例值 |
|---|---|---|
$uri | 当前请求的 URI | /api/users |
$request_uri | 完整请求 URI(含参数) | /api/users?page=1 |
$request_method | 请求方法 | GET, POST |
$scheme | 协议 | http, https |
$host | 主机名 | example.com |
$remote_addr | 客户端 IP | 192.168.1.100 |
$server_port | 服务器端口 | 80, 443 |
$http_referer | 来源页面 | https://google.com |
$http_user_agent | 客户端信息 | Mozilla/5.0… |
$cookie_name | Cookie 值 | session_id=abc123 |
5.2 正则捕获变量
使用正则表达式捕获 URL 中的值:
location ~ ^/user/(\d+)/profile$ { # $1 捕获用户 ID set $user_id $1; proxy_pass http://backend/users/$user_id; } location ~ ^/blog/(\d{4})/(\d{2})/(.+)$ { # $1: 年份, $2: 月份, $3: 文章标题 set $year $1; set $month $2; set $slug $3; proxy_pass http://backend/blog?year=$year&month=$month&slug=$slug; }5.3 自定义变量
使用set指令创建自定义变量:
server { listen 80; server_name example.com; # 根据请求方法设置变量 set $api_version "v1"; if ($arg_version = "v2") { set $api_version "v2"; } # 根据 User-Agent 设置变量 set $device_type "desktop"; if ($http_user_agent ~* "mobile") { set $device_type "mobile"; } location /api { proxy_pass http://backend/$api_version/$device_type; } }5.4 map 指令
使用map创建变量映射:
http { # 根据设备类型映射 map $http_user_agent $device_type { default "desktop"; "~*mobile" "mobile"; "~*tablet" "tablet"; } # 根据协议映射 map $scheme $is_https { default 0; https 1; } server { listen 80; server_name example.com; location / { proxy_pass http://backend/$device_type; proxy_set_header X-Device-Type $device_type; proxy_set_header X-HTTPS $is_https; } } }💡提示:
map指令比if更安全、更高效,推荐优先使用。
6. 实战案例
实战 1:图片服务器
server { listen 80; server_name images.example.com; root /var/www/images; # 按尺寸重写 location ~ ^/images/(.*)_(large|medium|small)\.(jpg|png)$ { set $filename $1; set $size $2; set $ext $3; # 检查缩略图是否存在 if (!-f $request_filename) { # 不存在则重写为原图 rewrite ^/images/(.*)_(large|medium|small)\.(jpg|png)$ /images/$1.$3 last; } } # 匹配所有图片 location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ { expires 30d; add_header Cache-Control "public, immutable"; } }项目知识点:
- 正则捕获组
- 文件存在判断
- 条件重写
实战 2:API 网关路由
server { listen 80; server_name gateway.example.com; # 版本路由 location ~ ^/api/v(\d+)/(.*)$ { set $version $1; set $path $2; # 根据版本转发到不同后端 if ($version = "1") { proxy_pass http://backend_v1/$path; } if ($version = "2") { proxy_pass http://backend_v2/$path; } proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 默认版本 location /api { rewrite ^/api/(.*)$ /api/v2/$1 last; } }项目知识点:
- 版本路由
- 条件判断
- 重写规则
实战 3:多语言网站
server { listen 80; server_name example.com; root /var/www/html; # 语言前缀匹配 location ~ ^/(en|zh|ja|ko)/(.*)$ { set $lang $1; set $path $2; # 检查语言文件是否存在 if (-f $document_root/$lang/$path) { rewrite ^/(en|zh|ja|ko)/(.*)$ /$lang/$2 last; } # 不存在则使用默认语言 rewrite ^/(en|zh|ja|ko)/(.*)$ /zh/$2 last; } # 默认语言 location / { try_files $uri $uri/ /zh/index.html; } }项目知识点:
- 多语言路由
- 条件判断
- 默认回退
实战 4:防盗链配置
server { listen 80; server_name images.example.com; root /var/www/images; location ~* \.(jpg|jpeg|png|gif|svg)$ { # 检查 Referer valid_referers none blocked server_names *.example.com; if ($invalid_referer) { # 返回防盗链图片 rewrite ^/ /watermark.jpg last; } expires 30d; add_header Cache-Control "public, immutable"; } }项目知识点:
- Referer 验证
- 条件重写
- 防盗链
7. 常见问题 FAQ
Q1:location 匹配顺序是什么?
A:匹配优先级:
1. = 精确匹配 2. ^~ 优先前缀匹配 3. ~ 区分大小写正则 4. ~* 不区分大小写正则 5. / 通用前缀匹配Q2:rewrite 的 last 和 break 有什么区别?
A:区别如下:
last:重写后继续匹配新的 locationbreak:重写后停止匹配,直接处理当前 location
# last:重写后继续匹配 rewrite ^/old/(.*)$ /new/$1 last; # break:重写后停止 rewrite ^/old/(.*)$ /new/$1 break;Q3:如何在 rewrite 中使用捕获组?
A:使用()捕获,$1,$2引用:
rewrite ^/user/(\d+)/profile$ /api/user?id=$1 last;Q4:if 指令有什么注意事项?
A:Nginx 官方建议:
- 尽量少用
if if中只能使用return,rewrite,proxy_pass等少数指令- 优先使用
map替代if
Q5:如何调试正则表达式?
A:使用以下方法:
# 测试配置语法nginx-t# 查看错误日志tail-f/var/log/nginx/error.log# 使用在线工具测试# https://regex101.com/8. 学习资源与建议
学习建议
1.先理解匹配优先级:理解 location 匹配顺序是核心
2.多用 map 少用 if:map更安全、更高效
3.善用测试命令:每次修改配置后,先用nginx -t测试语法
4.查看日志排错:遇到问题时,查看错误日志是最快的排错方法
5.使用在线工具:使用 regex101.com 测试正则表达式
官方资源
- Nginx rewrite 模块
- Nginx location 模块
- PCRE 文档
- Regex101 - 正则表达式测试工具
推荐工具
- Regex101 - 正则表达式测试
- Nginx Config - Nginx 配置生成器
- curl - HTTP 请求测试