Nginx valid_referers 配置陷阱:一次生产环境部署的血泪教训
一次看似简单的 Nginx 配置,却让我调试了整整两个小时...
背景
在企业官网部署场景中,API 访问控制是一个常见需求:API 接口只能被官网和管理后台调用,禁止外部直接访问。
最直接的方案就是在 Nginx 层面做 Referer 校验,但配置过程中遇到了意想不到的问题。
问题分析
错误配置
location /api/ {
valid_referers none blocked server_names example.com www.example.com;
if ($invalid_referer) {
return 403;
}
proxy_pass http://backend:18080;
}
看起来很合理对吧?结果 Nginx 启动直接报错:
nginx: [emerg] conflicting parameter "example.com" in /etc/nginx/nginx.conf:37
排查过程
第一阶段:怀疑文件编码
检查了 UTF-8 编码、换行符、隐藏字符...都没问题。
第二阶段:怀疑挂载问题
Docker volume 挂载、文件权限...也都正常。
第三阶段:逐步排除
创建一个最简单的配置:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://frontend;
}
}
这个可以工作!然后逐步添加配置,最终定位到问题出在 valid_referers 这一行。
根本原因
valid_referers 指令中直接写域名会与 server_name 参数冲突!
因为 server_names 已经包含了当前 server 块中定义的域名,再直接写域名会被 Nginx 解析为重复定义。
解决方案
正确配置
location /api/ {
# 使用正则表达式匹配域名
valid_referers none blocked server_names ~\.example\.com;
if ($invalid_referer) {
return 403;
}
proxy_pass http://backend:18080;
}
关键点:使用正则表达式 ~\.example\.com 来匹配域名。
valid_referers 参数说明
| 参数 | 说明 |
|---|---|
none | 允许没有 Referer 头的请求 |
blocked | 允许 Referer 被防火墙修改的请求 |
server_names | 允许 Referer 为当前 server_name |
~\.example\.com | 正则匹配,允许所有 example.com 子域名 |
实践要点
完整安全配置
# 禁止 IP 直接访问
server {
listen 80 default_server;
server_name _;
return 444;
}
# HTTP 强制跳转 HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
# 主服务
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/nginx/ssl/example.com.pem;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# API 接口 - Referer 校验
location /api/ {
valid_referers none blocked server_names ~\.example\.com;
if ($invalid_referer) {
return 403;
}
proxy_pass http://backend:18080;
}
# 管理后台
location /admin/ {
alias /usr/share/nginx/admin/;
try_files $uri $uri/ /admin/index.html;
}
# 官网
location / {
proxy_pass http://frontend;
}
}
验证测试
# 外部 Referer - 应返回 403
curl -H 'Referer: https://evil.com/' https://example.com/api/config
# HTTP/1.1 403 Forbidden ✅
# 官网 Referer - 应正常访问
curl -H 'Referer: https://example.com/' https://example.com/api/config
# HTTP/1.1 200 OK ✅
常见问题
Q1: 为什么不能直接写域名?
server_names 参数已经包含了当前 server 块中定义的所有域名,再直接写域名会导致重复定义冲突。使用正则表达式可以避免这个问题,同时还能匹配所有子域名。
Q2: 如何处理 Referer 为空的情况?
配置中的 none 参数允许没有 Referer 头的请求通过。这在以下场景很有用:
- 用户直接在地址栏输入 URL
- 书签访问
- 某些浏览器隐私模式
Q3: Referer 校验安全吗?
Referer 可以被伪造,但有一定门槛。建议采用多层防护策略:
- Nginx Referer 校验:阻止简单的直接调用
- Rate Limiting:限制请求频率
- JWT 认证:敏感接口必须登录
- CORS 配置:限制跨域请求来源
总结
- Nginx 配置语法很严格,某些指令的参数组合会产生冲突
- 正则表达式是解决域名匹配问题的好方法,使用
~\.domain\.com格式 - 遇到问题要逐步排除,从最简单的配置开始验证
踩坑时间:2小时
解决方案:1行代码
教训:RTFM (Read The F**king Manual) 📖
本文由无边界科技技术团队分享,专注软件开发与技术解决方案。
官网:wubianj.com
© 版权归无边界科技所有,转载请注明出处。