直接回答:Nginx 配置的难点不在指令本身,而在两件事——location 的匹配顺序和proxy_pass 末尾有没有斜杠。把这两点搞清楚,反向代理、动静分离、缓存、限流这些常见场景基本都是照抄几行的事。

配置文件是怎么组织的

Nginx 的配置是分层的,从上到下依次是:

层级 作用
main 全局生效,比如运行用户、工作进程数、日志路径
events 网络连接模型,比如每个进程的最大连接数、是否开启多路复用
http 所有站点的公共部分:MIME 类型、日志格式、gzip、ssl 全局设置
server 一个站点,由 listen 和 server_name 决定服务哪个域名和端口
location 站点里的路径匹配规则

主配置通常在 /etc/nginx/nginx.conf,各站点拆到 conf.d/ 或 sites-enabled/ 里再 include 进来。改配置优先改站点文件,别去动主配置——升级时主配置容易被覆盖。

还有一条容易忽略的规则:多数指令是"就近继承"的。http 里写了 client_max_body_size 20m,server 没写就用 20m;server 里再写一个,就在这个站点内覆盖掉。

location 的匹配顺序:配错了最难查

Nginx 收到请求后,按这个顺序决定用哪个 location:

  1. = 精确匹配,命中就结束;
  2. ^~ 前缀匹配,命中最长的那个之后不再看正则;
  3. ~ 和 ~* 正则匹配,按配置文件里出现的顺序,第一个命中就用它;
  4. 正则都没命中,回到刚才记住的最长前缀匹配。

所以下面这两条是常见误区:

  • 以为 location /api 会盖住 location /,其实只要后面有一条能命中的正则(比如 .php$),正则就会把前缀抢走;
  • 正则的顺序有意义,\.png$ 写在 \.(png|jpg)$ 前面,后者永远轮不到。

不确定的时候,在 location 里加一行 add_header X-Matched-Location "$uri"; 之类的标记头,用 curl -I 看实际命中了哪一条,比盯着配置猜快得多。

反向代理:最常用的那几行

1
2
3
4
5
6
7
8
9
10
location /api/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_read_timeout 60s;
}

几个关键点:

  • proxy_pass 末尾有没有斜杠决定路径怎么拼。 写 http://host/ 会把 location 匹配到的前缀替换掉(/api/user → /user);写 http://host 则整个原始路径原样转发(/api/user → /api/user)。后端 404 或路径多了一段,基本都在这儿。
  • Host 透传不能少。 不回传 Host,后端拿到的就是 upstream 里的地址,虚拟主机、绝对地址跳转、签名校验都可能出问题。
  • 真实 IP 要一层层加。 X-Forwarded-For 是可以伪造的,只有直接相连的那一跳可信,业务侧取真实 IP 时要从右往左数过可信代理的层数。
  • 长连接和 SSE 要清空 Connection 头。 HTTP/1.1 默认 keep-alive,写死 Connection: close 会让 WebSocket 和 SSE 断掉。

负载均衡

1
2
3
4
5
6
7
upstream backend {
least_conn;
server 10.0.0.11:8080 weight=3 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8080;
server 10.0.0.13:8080 backup;
keepalive 32;
}
  • 默认是轮询;weight 加权,ip_hash 按客户端 IP 固定后端,least_conn 给连接数最少的。
  • backup 只在前面的机器都挂了才启用,适合做冷备。
  • 开源版没有主动健康检查,max_fails / fail_timeout 是被动探测:连续失败几次就先摘掉,过一段时间再放回来。要主动探测得用商业版或者 OpenResty 那套。
  • 配置了 keepalive,server 后面必须跟 proxy_http_version 1.1 和 proxy_set_header Connection "",否则长连接不生效。

动静分离与静态资源缓存

静态资源交给 Nginx 直接读文件,动态请求转给应用:

1
2
3
4
5
6
location ~* \.(?:js|css|png|jpg|jpeg|gif|svg|webp|woff2?)$ {
root /srv/www/static;
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
access_log off;
}

缓存策略按"文件名会不会变"来分:

  • 带内容哈希的资源(app.9f3c1a.js)可以设一年加 immutable;
  • 不带哈希的图片、字体给几天到 30 天;
  • index.html 和接口响应不要长缓存,否则发了新版用户还是旧的。

gzip:压什么、不压什么

1
2
3
4
5
gzip on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types text/plain text/css application/javascript application/json application/xml image/svg+xml;
gzip_vary on;

图片、视频本身就是压缩格式,再 gzip 是白费 CPU。gzip_comp_level 超过 6 之后体积收益很小,但 CPU 消耗明显上升。静态文件还可以用 gzip_static 直接读预压缩好的 .gz,但那需要单独编译模块。

HTTPS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
listen 443 ssl;
http2 on;
server_name example.com;

ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;

add_header Strict-Transport-Security "max-age=31536000" always;
}

server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}

fullchain.pem 要包含中间证书,只有服务器证书的话,部分客户端会因为链不完整而报错。加了 HSTS 之后浏览器会强制走 HTTPS,确认整站都能用 HTTPS 再加,否则回不去。

访问控制与限流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 只允许内网访问后台
location /admin/ {
allow 10.0.0.0/8;
deny all;
}

# 限速:同一 IP 每秒 10 个请求,允许突发 20 个
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api/ {
limit_req zone=api burst=20 nodelay;
}

# 限制并发连接数
limit_conn_zone $binary_remote_addr zone=conn:10m;
location /download/ {
limit_conn conn 5;
}

limit_req_zone 只能写在 http 层,limit_req 写在 server 或 location 层,写错位置会直接报语法错误。nodelay 决定突发请求是立刻处理还是排队等待,接口类建议加上。

配完一定要自查的几件事

  1. nginx -t 通过再 reload,改坏了语法会直接导致 reload 失败;
  2. 用 nginx -s reload 而不是 restart——同机其他站点不会被中断;
  3. 上传接口记得调 client_max_body_size,默认 1M 会让大文件请求直接 413;
  4. 反向代理后,应用里取到的 IP 和协议是代理的,要把 X-Real-IP、X-Forwarded-Proto 透传并让应用按可信代理解析;
  5. 加了缓存头之后,用 curl -I 确认 Cache-Control、Expires 真的是你想要的值。

内置变量速查

变量 含义
$host 请求头里的主机名,没有则用 server_name
$uri 当前请求的路径,不含参数,会被 rewrite 改写
$request_uri 原始请求行里的完整路径,含参数,不会变
$args 查询字符串
$remote_addr 客户端地址(经过代理时是上一跳的地址)
$scheme http 或 https
$http_xxx 任意请求头,- 换成 _,如 $http_user_agent

对外跳转尽量用 $request_uri 而不是 $uri,否则参数会丢。


本文整理自我自己早先的 Nginx 笔记和公开资料(主要参考了 chrootliu 的《Nginx 最全配置讲解》与网上流传的一份 Nginx 学习手册),文字由 AI 协助改写后经我复核。配置以所用 Nginx 版本的官方文档为准。

这篇笔记整理自我自己的实践记录,如果做法有出入,或者你踩过别的坑,欢迎到留言板一起聊聊。

站内搜索

没有找到内容!