访问控制与限流
allow/deny 的顺序语义、auth_basic 与 auth_request、漏桶限流的 burst/nodelay 与连接数限制。
allow / deny
语法: allow address | CIDR | unix: | all;
deny address | CIDR | unix: | all;
上下文: http, server, location, limit_exceptlocation /admin/ {
allow 192.168.1.0/24;
allow 10.0.0.1;
deny all;
}按书写顺序自上而下匹配,第一条命中即生效。 所以 deny all; 必须写在最后——写在前面会把后面的 allow 全废掉。
拒绝时返回 403。
allow/deny 判断的是 $remote_addr——即直连 nginx 的对端地址。
nginx 前面有 CDN 或负载均衡时,$remote_addr 是那台代理的 IP,不是真实用户 IP。此时基于 IP 的访问控制会全部失效(要么全放行要么全拒绝)。
解法是 ngx_http_realip_module:
set_real_ip_from 10.0.0.0/8; # 信任的代理网段
real_ip_header X-Forwarded-For;
real_ip_recursive on;它会按信任列表从 X-Forwarded-For 中还原真实 IP 并改写 $remote_addr。
set_real_ip_from 必须精确列出可信代理——写成 0.0.0.0/0 等于让任何人都能伪造自己的 IP。这与 HTTP 转发字段的安全模型是同一条原则。
limit_except
location /upload/ {
limit_except GET HEAD {
allow 192.168.1.0/24;
deny all;
}
}语义是反的:限制除了列出方法之外的所有方法。上面表示 GET/HEAD 任何人可用,其他方法(POST/PUT/DELETE)只允许内网。
注意 GET 会自动包含 HEAD(HTTP 语义上 HEAD 是 GET 的无体版本)。
auth_basic
语法: auth_basic string | off; 默认: off
auth_basic_user_file file;
上下文: http, server, location, limit_exceptlocation /private/ {
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
}那个字符串是 WWW-Authenticate 挑战里的 realm,会显示在浏览器弹窗里。
密码文件用 htpasswd 生成(支持 bcrypt、apr1、crypt):
htpasswd -B -c /etc/nginx/.htpasswd aliceBasic 认证的 Base64 不是加密——必须在 TLS 上使用,否则等于明文传密码。
嵌套 location 里要关掉继承的认证,用 auth_basic off;。
auth_request
location /private/ {
auth_request /auth;
proxy_pass http://backend;
}
location = /auth {
internal;
proxy_pass http://auth-service/verify;
proxy_pass_request_body off; # 不转发请求体
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}(需 --with-http_auth_request_module)
把鉴权委托给一个子请求:
| 子请求返回 | nginx 行为 |
|---|---|
2xx | 放行 |
401 / 403 | 拒绝,原样返回该状态 |
| 其他 | 500 |
要点:
internal;让/auth只能被内部子请求访问,外部直接请求返回 404。proxy_pass_request_body off避免把请求体(可能很大)也发给鉴权服务。- 用
auth_request_set把鉴权服务返回的信息传给上游:
auth_request_set $user $upstream_http_x_user;
proxy_set_header X-User $user;这是把 SSO/OAuth 网关接进 nginx 的标准模式。
limit_req:漏桶限流
语法: limit_req_zone key zone=name:size rate=rate [sync];
上下文: http
语法: limit_req zone=name [burst=number] [nodelay | delay=number];
上下文: http, server, locationhttp {
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
server {
location /api/ {
limit_req zone=one burst=20 nodelay;
}
}
}官方明确说明用的是 漏桶(leaky bucket) 算法。
key 用 $binary_remote_addr
不要用 $remote_addr——它是点分十进制字符串(IPv4 最多 15 字节),而 $binary_remote_addr 是二进制形式(IPv4 仅 4 字节)。同样的 zone 大小能存更多状态。
zone 容量(官方给的数字):
| 平台 | 每状态 | 每 MB 状态数 |
|---|---|---|
| 32 位 | 64 字节 | 约 16000 |
| 64 位 | 128 字节 | 约 8000 |
所以 zone=one:10m 在 64 位上约存 8 万个 IP。zone 满了新请求会被拒绝(返回 503),且会记 error 日志。
rate
rate=10r/s; # 每秒 10 个
rate=30r/m; # 每分钟 30 个 = 0.5r/sr/m 用于低频限制——rate=1r/m 比 rate=0.016r/s 好写得多(后者也不合法)。
nginx 内部按毫秒粒度平滑:10r/s 实际是「每 100ms 允许 1 个」,而不是「每秒开头放 10 个」。
burst 与 nodelay
这是最需要理解的一对:
| 配置 | 行为 |
|---|---|
limit_req zone=one; | 严格 10r/s,超出立即 503 |
limit_req zone=one burst=20; | 超出的排队延迟处理,队列满 20 才 503 |
limit_req zone=one burst=20 nodelay; | 超出的立即处理不排队,但仍占用桶容量 |
limit_req zone=one burst=20 delay=10; | 前 10 个立即处理,第 11–20 个延迟,超过 20 拒绝 |
没有 burst 时限流极其严酷:页面加载会同时发几十个请求,10r/s 会把绝大部分打成 503。真实场景基本都要配 burst。
nodelay 通常是想要的:允许突发但维持长期平均速率。不加 nodelay 时请求会被人为延迟,用户感觉是「网站变慢」而不是「被限流」。
状态码与日志
limit_req_status 503; 默认
limit_req_log_level error; 默认(延迟的记低一级 = warn)建议改成 429——429 Too Many Requests 才是语义正确的限流状态码,503 表示「服务不可用」会误导客户端与监控:
limit_req_status 429;干跑模式
limit_req_dry_run on;只统计不拦截。上线新限流规则前先干跑一段时间,配合 $limit_req_status 变量观察会拦掉多少:
log_format limit '$remote_addr "$request" limit=$limit_req_status';$limit_req_status 取值:PASSED、DELAYED、REJECTED、DELAYED_DRY_RUN、REJECTED_DRY_RUN。
limit_conn:并发连接数
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
limit_conn perip 10; # 每 IP 最多 10 个并发连接
limit_conn perserver 1000; # 本站总共最多 1000
}与 limit_req 的区别:限的是「同时打开的连接数」而非「请求速率」。对下载站、流媒体更合适。
limit_conn_status 同样建议改 429。
limit_rate:带宽限速
location /download/ {
limit_rate 500k; # 每连接 500 KB/s
limit_rate_after 10m; # 前 10 MB 不限速
}limit_rate_after 很实用:小文件全速,大文件才限速——既保证网页体验又防止大文件占满带宽。
注意限的是每个连接,一个客户端开 5 个连接就能拿到 5 倍带宽——要配合 limit_conn 使用。
可以用变量动态限速:
map $http_user_agent $rate {
default 1m;
~*bot 100k;
}
limit_rate $rate;组合示例
http {
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/m;
limit_conn_zone $binary_remote_addr zone=conn:10m;
limit_req_status 429;
limit_conn_status 429;
server {
# 还原真实 IP(前面有 CDN 时必需)
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
limit_conn conn 20;
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
# 登录接口严格限流,防撞库
location = /login {
limit_req zone=login burst=3;
proxy_pass http://backend;
}
location /admin/ {
allow 10.0.0.0/8;
deny all;
auth_basic "Admin";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://backend;
}
location /download/ {
limit_rate 1m;
limit_rate_after 10m;
}
}
}下一篇:日志。