将 docs/ 根目录下的 nginx 相关文档统一移动到 docs/nginx/ 子目录, 提高文档组织性和可维护性。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8.9 KiB
8.9 KiB
NGINX URL 重写与请求处理指南
1. rewrite 模块概述
ngx_http_rewrite_module 模块用于:
- 使用 PCRE 正则表达式更改请求 URI
- 返回重定向
- 有条件地选择配置
处理顺序
- 按顺序执行 server 级别的 rewrite 指令
- 根据请求 URI 搜索 location
- 按顺序执行 location 内的 rewrite 指令
- 如果 URI 被 rewrite 更改,重复循环(不超过 10 次)
2. rewrite 指令
语法
rewrite regex replacement [flag];
# 基础重写
rewrite ^/old/(.*)$ /new/$1 permanent;
# 重定向
rewrite ^/download/(.*)$ /files/$1 redirect;
# 内部重写
rewrite ^/images/(.*)\.jpg$ /pics/$1.png last;
Flags 参数
| Flag | 说明 |
|---|---|
last |
停止当前指令集处理,开始搜索新 location |
break |
停止当前指令集处理,继续在当前 location 中处理 |
redirect |
返回 302 临时重定向 |
permanent |
返回 301 永久重定向 |
示例
server {
# Server 级别 - 使用 last
rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last;
rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra last;
location /download/ {
# Location 级别 - 使用 break
rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 break;
rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra break;
}
}
参数处理
# replacement 包含新参数时,旧参数自动追加
rewrite ^/users/(.*)$ /show?user=$1? last;
# ? 结尾表示不追加旧参数
3. return 指令
语法
return code [text];
return code URL;
return URL;
状态码使用
| 状态码 | 类型 | 用途 |
|---|---|---|
| 301 | 永久重定向 | URL 永久变更 |
| 302 | 临时重定向 | URL 临时变更 |
| 303 | 临时重定向 | POST 后重定向到 GET |
| 307 | 临时重定向 | 保持请求方法 |
| 308 | 永久重定向 | 保持请求方法 |
| 444 | 非标准 | 关闭连接(不发送响应) |
示例
# 返回文本
location /status {
return 200 "OK";
}
# 返回 JSON
location /json {
default_type application/json;
return 200 '{"status": "ok"}';
}
# 重定向
location /old {
return 301 /new;
}
# 域名重定向
server {
listen 80;
server_name old.example.com;
return 301 http://new.example.com$request_uri;
}
# HTTP 到 HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
# 拒绝请求
location /private {
return 403;
}
# 关闭连接(不发送响应)
server {
listen 80 default_server;
server_name _;
return 444;
}
4. if 指令
语法
if (condition) { ... }
条件类型
| 类型 | 示例 | 说明 |
|---|---|---|
| 变量判断 | if ($variable) |
空字符串或 "0" 为假 |
| 字符串相等 | if ($a = "value") |
等于 |
| 字符串不等 | if ($a != "value") |
不等于 |
| 正则匹配 | if ($a ~ pattern) |
区分大小写 |
| 正则匹配 | if ($a ~* pattern) |
不区分大小写 |
| 正则不匹配 | if ($a !~ pattern) |
不匹配 |
| 文件存在 | if (-f $uri) |
文件存在 |
| 文件不存在 | if (!-f $uri) |
文件不存在 |
| 目录存在 | if (-d $uri) |
目录存在 |
| 目录不存在 | if (!-d $uri) |
目录不存在 |
| 文件/目录存在 | if (-e $uri) |
存在 |
| 可执行 | if (-x $uri) |
可执行文件 |
示例
# 浏览器判断
if ($http_user_agent ~ MSIE) {
rewrite ^(.*)$ /msie/$1 break;
}
# 请求方法判断
if ($request_method = POST) {
return 405;
}
# 文件不存在时
if (!-f $request_filename) {
rewrite ^(.*)$ /index.php last;
}
# 防盗链
if ($invalid_referer) {
return 403;
}
# 限制速度
if ($slow) {
limit_rate 10k;
break;
}
注意事项
if块会创建单独的配置上下文- 避免在 if 中使用某些指令(可能导致意外行为)
- 推荐使用
try_files或map替代复杂条件判断
5. break 指令
停止处理当前 rewrite 模块指令集。
location /download/ {
if ($forbidden) {
return 403;
}
if ($slow) {
limit_rate 10k;
break; # 停止 rewrite 模块处理,继续 location 处理
}
rewrite ^(.*)$ /files/$1 break;
}
6. set 指令
设置变量值。
set $variable value;
# 示例
set $mobile false;
if ($http_user_agent ~ "Mobile") {
set $mobile true;
}
location /api {
if ($mobile = true) {
proxy_pass http://mobile_backend;
}
proxy_pass http://desktop_backend;
}
7. map 指令(替代复杂 if)
语法
map source $variable { ... }
示例
http {
# 浏览器映射
map $http_user_agent $mobile {
default false;
"~*Mobile" true;
"~*Android" true;
"~*iPhone" true;
}
# 环境映射
map $host $backend {
default http://backend.prod;
dev.example.com http://backend.dev;
test.example.com http://backend.test;
}
# 日志级别映射
map $status $loggable {
~^[23] 0; # 2xx/3xx 不记录
default 1;
}
server {
location / {
if ($mobile = true) {
proxy_pass http://mobile_backend;
}
proxy_pass $backend;
}
access_log /var/log/nginx/access.log combined if=$loggable;
}
}
8. 常见重写场景
域名重定向
# 旧域名到新域名
server {
listen 80;
server_name old.example.com;
return 301 http://new.example.com$request_uri;
}
# 多域名统一
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
# www 到非 www
server {
listen 80;
server_name www.example.com;
return 301 http://example.com$request_uri;
}
# 非 www 到 www
server {
listen 80;
server_name example.com;
return 301 http://www.example.com$request_uri;
}
HTTP 到 HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
# ...
}
URL 结构变更
# 产品页面重写
rewrite ^/products/([0-9]+)$ /product?id=$1 last;
# 分类页面
rewrite ^/category/([a-z]+)$ /cat?name=$1 last;
# API 版本迁移
rewrite ^/api/v1/(.*)$ /api/v2/$1 permanent;
静态文件处理
location / {
try_files $uri $uri/ @fallback;
}
location @fallback {
rewrite ^(.*)$ /index.php last;
}
# 图片路径重写
rewrite ^/images/(.*)\.(gif|jpg|png)$ /static/$1.$2 last;
PHP 应用重写
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass localhost:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
WordPress 重写
location / {
try_files $uri $uri/ /index.php?$args;
}
# WordPress 固定链接支持
rewrite /wp-admin$ /wp-admin/ permanent;
9. 防盗链配置
location ~* \.(gif|jpg|png|swf|flv)$ {
valid_referers none blocked server_names *.example.com example.*;
if ($invalid_referer) {
return 403;
# 或返回替代图片
# return 301 http://example.com/nolink.png;
}
}
10. rewrite_log 指令
启用重写日志记录。
rewrite_log on; # 将 rewrite 处理结果记录到 error_log(notice 级别)
11. 内部实现说明
rewrite 指令在配置阶段编译为内部指令,请求处理时由虚拟栈机器解释执行。
配置示例编译结果:
location /download/ {
if ($forbidden) {
return 403;
}
rewrite ^/(.*)$ /files/$1 break;
}
编译为内部指令:
variable $forbidden
check against zero
return 403
end of code
match of regular expression
copy $1
copy "/files/"
end of regular expression
end of code
12. 最佳实践
优先使用 return 而非 rewrite
# 推荐
return 301 /new;
# 不推荐
rewrite ^(.*)$ /new permanent;
使用 try_files 替代复杂的 if 文件检查
# 推荐
location / {
try_files $uri $uri/ @fallback;
}
# 不推荐
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php last;
}
}
使用 map 替代重复的 if 判断
# 推荐
http {
map $http_user_agent $is_mobile {
default false;
"~*Mobile" true;
}
}
# 不推荐
location / {
if ($http_user_agent ~ Mobile) {
set $is_mobile true;
}
}
Location 级别使用 break
location /download/ {
rewrite ^(.*)$ /files/$1 break;
# 使用 break 而非 last,避免 10 次循环错误
}
注意循环限制
rewrite 循环不超过 10 次,超过会返回 500 错误。
# 错误示例(无限循环)
rewrite ^/test$ /test last;