将 docs/ 根目录下的 nginx 相关文档统一移动到 docs/nginx/ 子目录, 提高文档组织性和可维护性。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
516 lines
8.9 KiB
Markdown
516 lines
8.9 KiB
Markdown
# NGINX URL 重写与请求处理指南
|
||
|
||
## 1. rewrite 模块概述
|
||
|
||
`ngx_http_rewrite_module` 模块用于:
|
||
- 使用 PCRE 正则表达式更改请求 URI
|
||
- 返回重定向
|
||
- 有条件地选择配置
|
||
|
||
### 处理顺序
|
||
|
||
1. 按顺序执行 server 级别的 rewrite 指令
|
||
2. 根据请求 URI 搜索 location
|
||
3. 按顺序执行 location 内的 rewrite 指令
|
||
4. 如果 URI 被 rewrite 更改,重复循环(不超过 10 次)
|
||
|
||
---
|
||
|
||
## 2. rewrite 指令
|
||
|
||
### 语法
|
||
|
||
`rewrite regex replacement [flag];`
|
||
|
||
```nginx
|
||
# 基础重写
|
||
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 永久重定向 |
|
||
|
||
### 示例
|
||
|
||
```nginx
|
||
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;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 参数处理
|
||
|
||
```nginx
|
||
# replacement 包含新参数时,旧参数自动追加
|
||
rewrite ^/users/(.*)$ /show?user=$1? last;
|
||
# ? 结尾表示不追加旧参数
|
||
```
|
||
|
||
---
|
||
|
||
## 3. return 指令
|
||
|
||
### 语法
|
||
|
||
```nginx
|
||
return code [text];
|
||
return code URL;
|
||
return URL;
|
||
```
|
||
|
||
### 状态码使用
|
||
|
||
| 状态码 | 类型 | 用途 |
|
||
|--------|------|------|
|
||
| 301 | 永久重定向 | URL 永久变更 |
|
||
| 302 | 临时重定向 | URL 临时变更 |
|
||
| 303 | 临时重定向 | POST 后重定向到 GET |
|
||
| 307 | 临时重定向 | 保持请求方法 |
|
||
| 308 | 永久重定向 | 保持请求方法 |
|
||
| 444 | 非标准 | 关闭连接(不发送响应) |
|
||
|
||
### 示例
|
||
|
||
```nginx
|
||
# 返回文本
|
||
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)` | 可执行文件 |
|
||
|
||
### 示例
|
||
|
||
```nginx
|
||
# 浏览器判断
|
||
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 模块指令集。
|
||
|
||
```nginx
|
||
location /download/ {
|
||
if ($forbidden) {
|
||
return 403;
|
||
}
|
||
|
||
if ($slow) {
|
||
limit_rate 10k;
|
||
break; # 停止 rewrite 模块处理,继续 location 处理
|
||
}
|
||
|
||
rewrite ^(.*)$ /files/$1 break;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. set 指令
|
||
|
||
设置变量值。
|
||
|
||
```nginx
|
||
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 { ... }`
|
||
|
||
### 示例
|
||
|
||
```nginx
|
||
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. 常见重写场景
|
||
|
||
### 域名重定向
|
||
|
||
```nginx
|
||
# 旧域名到新域名
|
||
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
|
||
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name example.com;
|
||
return 301 https://$server_name$request_uri;
|
||
}
|
||
|
||
server {
|
||
listen 443 ssl;
|
||
server_name example.com;
|
||
# ...
|
||
}
|
||
```
|
||
|
||
### URL 结构变更
|
||
|
||
```nginx
|
||
# 产品页面重写
|
||
rewrite ^/products/([0-9]+)$ /product?id=$1 last;
|
||
|
||
# 分类页面
|
||
rewrite ^/category/([a-z]+)$ /cat?name=$1 last;
|
||
|
||
# API 版本迁移
|
||
rewrite ^/api/v1/(.*)$ /api/v2/$1 permanent;
|
||
```
|
||
|
||
### 静态文件处理
|
||
|
||
```nginx
|
||
location / {
|
||
try_files $uri $uri/ @fallback;
|
||
}
|
||
|
||
location @fallback {
|
||
rewrite ^(.*)$ /index.php last;
|
||
}
|
||
|
||
# 图片路径重写
|
||
rewrite ^/images/(.*)\.(gif|jpg|png)$ /static/$1.$2 last;
|
||
```
|
||
|
||
### PHP 应用重写
|
||
|
||
```nginx
|
||
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 重写
|
||
|
||
```nginx
|
||
location / {
|
||
try_files $uri $uri/ /index.php?$args;
|
||
}
|
||
|
||
# WordPress 固定链接支持
|
||
rewrite /wp-admin$ /wp-admin/ permanent;
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 防盗链配置
|
||
|
||
```nginx
|
||
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 指令
|
||
|
||
启用重写日志记录。
|
||
|
||
```nginx
|
||
rewrite_log on; # 将 rewrite 处理结果记录到 error_log(notice 级别)
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 内部实现说明
|
||
|
||
rewrite 指令在配置阶段编译为内部指令,请求处理时由虚拟栈机器解释执行。
|
||
|
||
**配置示例编译结果**:
|
||
|
||
```nginx
|
||
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
|
||
|
||
```nginx
|
||
# 推荐
|
||
return 301 /new;
|
||
|
||
# 不推荐
|
||
rewrite ^(.*)$ /new permanent;
|
||
```
|
||
|
||
### 使用 try_files 替代复杂的 if 文件检查
|
||
|
||
```nginx
|
||
# 推荐
|
||
location / {
|
||
try_files $uri $uri/ @fallback;
|
||
}
|
||
|
||
# 不推荐
|
||
location / {
|
||
if (!-e $request_filename) {
|
||
rewrite ^(.*)$ /index.php last;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 使用 map 替代重复的 if 判断
|
||
|
||
```nginx
|
||
# 推荐
|
||
http {
|
||
map $http_user_agent $is_mobile {
|
||
default false;
|
||
"~*Mobile" true;
|
||
}
|
||
}
|
||
|
||
# 不推荐
|
||
location / {
|
||
if ($http_user_agent ~ Mobile) {
|
||
set $is_mobile true;
|
||
}
|
||
}
|
||
```
|
||
|
||
### Location 级别使用 break
|
||
|
||
```nginx
|
||
location /download/ {
|
||
rewrite ^(.*)$ /files/$1 break;
|
||
# 使用 break 而非 last,避免 10 次循环错误
|
||
}
|
||
```
|
||
|
||
### 注意循环限制
|
||
|
||
rewrite 循环不超过 10 次,超过会返回 500 错误。
|
||
|
||
```nginx
|
||
# 错误示例(无限循环)
|
||
rewrite ^/test$ /test last;
|
||
``` |