写 Nginx 配置,最头疼的不是语法,而是 “我的请求到底走到了哪一步”。看了几篇博客,抄了几段配置,上线就挂。
今天把 Nginx 配置文件的核心参数从头捋一遍。从 server 块的五个核心指令、location 匹配优先级、try_files 万能公式,到 if 指令的注意事项和 proxy_pass 的反斜杠陷阱。
一、server 块的五个核心指令
一个 server 块,记住这五个就够了:
server {
listen 80; # 监听端口
server_name example.com; # 域名匹配
root /var/www; # 文件根目录
index index.html; # 默认首页
location { } # 路由规则(核心)
}listen 支持多个端口、IP 绑定、SSL:
listen 80;
listen 443 ssl;
listen 8081 default_server; # 兜底 serverserver_name 支持通配和正则:
server_name example.com *.example.com ~^www\.(?.+)\.example\.com$;二、location 匹配优先级
这是最容易搞混的地方。优先级从高到低:
| 写法 | 含义 | 优先级 |
|---|---|---|
| `location = /page` | 精确匹配 | 最高 |
| `location ^~ /img/` | 前缀匹配,不检查正则 | 次高 |
| `location ~ \.jpg$` | 正则匹配(区分大小写) | 中 |
| `location ~* \.jpg$` | 正则匹配(不区分大小写) | 中 |
| `location /abc/` | 普通前缀匹配 | 低 |
关键规则
1. = 优先于一切
location = / { return 200 "exact\n"; } # / → 命中
location / { return 200 "default\n"; } # /anything → 命中2. ^~ 赢了所有正则
location ^~ /img/ { return 200 "prefix\n"; } # /img/logo.png → 命中
location ~ \.png$ { return 200 "regex\n"; } # 不会命中3. 多个正则只取第一个
location ~ /a { return 200 "a\n"; } # 命中
location ~ /ab { return 200 "ab\n"; } # 永远不会命中记忆口诀:等号最贵,波浪第二,正则取第一个,前缀排最后。
三、`try_files` — 万能公式
location / {
try_files $uri $uri/ /index.php?$query_string;
}这是 WordPress / Laravel / 所有 PHP 框架的伪静态标配。
`try_files` 的工作原理
- **`$uri`** → 检查本地有没有这个文件(如 `/robots.txt`)
- **`$uri/`** → 没有的话检查本地有没有这个目录(如 `/about/`)
- **`/index.php?$query_string`** → 都没找到,把请求交给 `index.php`,把原始 query string 透传过去
最后一项必须是 `=404` 或有效路径
# 错误写法,最后一项没有 =404
location / {
try_files $uri $uri/ /index.php?$args;
}如果 /index.php 不存在(比如纯静态站),请求会进入无限递归,最终 500。
# 正确写法
location / {
try_files $uri $uri/ /index.php?$args =404;
}四、`if` 的注意事项
Nginx 官方文档有句话:"if is evil"。不是不能用,是要知道它的行为。
`if` 的匹配是顺序执行的
# 想排除 /admin,但不行
location / {
if ($uri ~ "^/admin") {
return 403;
}
root /var/www;
}这段代码 不会生效——if 块里的 root 指令会被提升到 http 块作用域。
正确用法:`if` 只用于 return / rewrite / set
# 正确:set 变量
if ($http_user_agent ~* "bot|spider") {
set $sp_go 1;
}
# 正确:return 状态码
if ($host = "old.example.com") {
return 301 https://new.example.com$request_uri;
}
# 正确:rewrite
if ($sp_go = 1) {
rewrite ^ /__sp_proxy$request_uri last;
}实战:爬虫代理 + WordPress 白名单
这是我正在用的一个真实配置,把蜘蛛流量代理到主站,真人走本地 WordPress,同时不拦截 WordPress API 和后台:
set $sp_go 0;
# 蜘蛛任意路径 → 代理
if ($http_user_agent ~* "bot|spider") {
set $sp_go 1;
}
# 真人 + 本地无文件 → 代理
if (!-e $request_filename) {
set $sp_go 1;
}
# WordPress 路由走本地 PHP,不走代理
# 注意:这段必须放在上面两个 if 之后,后设覆盖先设
if ($uri ~ "^/(wp-json|wp-admin|wp-content|wp-includes)") {
set $sp_go 0;
}
# 最终判断
if ($sp_go = 1) {
rewrite ^ /__sp_proxy$request_uri last;
}
# 代理 location
location ^~ /__sp_proxy/ {
rewrite ^/__sp_proxy/(.*)$ /$1 break;
proxy_pass http://brandhd.com;
proxy_set_header Host brandhd.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_http_version 1.1;
proxy_buffering off;
proxy_redirect off;
proxy_connect_timeout 15s;
proxy_send_timeout 30s;
proxy_read_timeout 60s;
}核心逻辑: Nginx 的 if 按顺序执行,最后一个 set 决定最终值。WordPress 白名单的 set $sp_go 0 覆盖前面的 set $sp_go 1,实现"优先走代理,但 WordPress 路由例外"。
五、`proxy_pass` 的反斜杠陷阱
这是最容易踩的坑,一个 / 之差,请求路径完全不同。
# 尾部有 / → 去掉匹配部分
location /api/ {
proxy_pass http://127.0.0.1:8080/;
# /api/user → upstream 收到 /user
}
# 尾部无 / → 完整传递
location /api/ {
proxy_pass http://127.0.0.1:8080;
# /api/user → upstream 收到 /api/user
}对比
| 配置 | 请求 | upstream 收到 |
|---|---|---|
| `proxy_pass .../;` | `/api/user` | `/user` |
| `proxy_pass ...;` | `/api/user` | `/api/user` |
记住一个原则:proxy_pass 末尾有没有斜杠,决定了 location 匹配的部分是否被去掉。
六、`root` vs `alias`
另一个经典混淆:
# root:拼接
location /img/ {
root /data;
# /img/logo.png → 读 /data/img/logo.png
}
# alias:替换
location /img/ {
alias /data/images/;
# /img/logo.png → 读 /data/images/logo.png
}alias 只能用在 location 块,不能用在 if 块。
七、常用内置变量速查
所有 $ 开头的变量都是 Nginx 内置的,不需要定义:
| 变量 | 含义 | 例子 |
|---|---|---|
| `$uri` | 当前 URI(不含参数) | `/page?id=1` → `/page` |
| `$request_uri` | 完整原始 URI | `/page?id=1` → `/page?id=1` |
| `$query_string` | 查询参数部分 | `id=1` |
| `$scheme` | 协议 | `https` |
| `$http_host` | 请求头 Host | `example.com` |
| `$remote_addr` | 客户端 IP | `1.2.3.4` |
| `$request_filename` | `root` + `$uri` 拼出的完整路径 | `/data/page.html` |
| `$request_method` | 请求方法 | `GET` |
| `$status` | 响应状态码 | `200` |
| `$time_local` | 本地时间 | `06/Aug/2026:15:00:00 +0800` |
完整变量列表(70+ 个):Nginx 官方文档
八、其他常用指令
access_log / error_log
# 关闭访问日志(大流量站点省磁盘)
access_log off;
# 写入指定文件
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;expires 静态资源缓存
location ~* \.(jpg|png|css|js|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}gzip 压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;限制并发
# 在 http 块定义
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
# 在 server/location 块使用
limit_req zone=api burst=20 nodelay;写在最后
Nginx 配置说到底就一个核心:请求来了,它走到哪个 location,然后发生了什么。
把 server → location → try_files/proxy_pass/rewrite 这条主干搞熟,其他都是加调料。上面这些参数,够日常运维 90% 的场景了。
