前言
很多运维同学在配置HTTP跳转HTTPS时,第一反应就是这句:
return 301 https://$host$request_uri;
看起来天衣无缝——$request_uri包含了完整的URI和查询参数,跳转后应该原样保留才对。但实际部署后,却发现UTM参数、搜索关键词、分页参数莫名其妙地消失了。
这篇文章不讲基础概念,直击3个让return 301 $request_uri参数丢失的隐藏陷阱,每个都附带复现条件和修复方法。
陷阱一:多层server块重写导致$request_uri被覆盖
问题复现
当Nginx配置中存在多个server块或location块对同一个请求做了重写时,$request_uri的值可能已经不是原始请求了。
# 80端口 - 第一层
server {
listen 80;
server_name example.com;
location /old-path {
rewrite ^/old-path(.*)$ /new-path$1 last;
}
# 这里 $request_uri 仍然是原始请求,但 rewrite 已经改变了 $uri
return 301 https://$host$request_uri;
}
上面这个配置看起来没问题,因为$request_uri记录的是原始请求URI,rewrite的last会重新匹配location,所以这里return 301不会被执行到。但如果你把return放在rewrite之后且没有用last,参数就有可能在中间环节被丢弃。
修复方法
确保HTTP到HTTPS的跳转是第一优先级,放在所有rewrite规则之前:
server {
listen 80;
server_name example.com;
# HTTPS跳转放在最前面,优先级最高
return 301 https://$host$request_uri;
# 以下规则永远不会执行到(上面的return已经返回)
location /old-path {
rewrite ^/old-path(.*)$ /new-path$1 permanent;
}
}
陷阱二:CDN或反向代理层修改了请求
问题复现
如果你在Nginx前面有CDN(如Cloudflare、阿里云CDN)或另一层反向代理,参数可能在到达Nginx之前就已经被改写了。
典型场景:
- CDN的"自动HTTPS重写"功能先做了一次302跳转,丢弃了查询参数
- 前层代理用
proxy_pass转发时没有传递原始X-Forwarded-URI - CDN的缓存规则把带参数的URL和不含参数的URL当作同一个资源
排查方法
在Nginx中添加日志,记录到达时$request_uri的值:
log_format debug_format '$remote_addr - $request_uri - $args';
access_log /var/log/nginx/debug.log debug_format;
用curl直接请求源站IP(绕过CDN),对比参数是否保留:
curl -v "http://源站IP/path?utm_source=test&ref=abc"
如果直连源站参数保留、走CDN后丢失,说明问题在CDN层。
修复方法
在CDN侧关闭"自动HTTPS重写",改为回源让Nginx处理跳转。或者在CDN的跳转规则中显式保留查询参数。
以Cloudflare为例,关闭"Always Use HTTPS"的页面规则,改用Nginx端处理:
# Cloudflare → Nginx 回源时带参数
# Nginx端正常配置即可
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
陷阱三:$request_uri和$uri$is_args$args混用导致参数重复或丢失
问题复现
有些配置同时出现了$request_uri和$uri$is_args$args,看似等效,但在某些边界条件下行为不同:
# 错误示例:混用两种变量
server {
listen 80;
# location内用了 $uri$is_args$args
location / {
return 301 https://$host$uri$is_args$args;
}
# 但如果有 rewrite 规则改了 $uri
# $uri 已经是重写后的值,但 $args 还是原始的
# 而 $request_uri 始终是原始请求
}
关键区别:
| 变量 | 是否受rewrite影响 | 是否包含问号 |
|---|---|---|
| $request_uri | 否(始终是原始请求) | 是(包含?和参数) |
| $uri$is_args$args | $uri受影响,$args不受 | $is_args自动判断 |
修复方法
对于HTTP到HTTPS的简单跳转,始终使用$request_uri,它是最可靠的选择:
# 推荐:简洁可靠
return 301 https://$host$request_uri;
只有在需要修改或过滤参数时,才用$uri$is_args$args:
# 需要过滤特定参数时
if ($args ~* "utm_source=([^&]+)") {
set $utm_source $1;
}
return 301 https://$host$uri?utm_source=$utm_source;
完整修复配置参考
一个经过验证的HTTP跳HTTPS配置,覆盖上述所有陷阱:
server {
listen 80;
server_name example.com www.example.com;
# 记录跳转前的原始请求,方便排查
access_log /var/log/nginx/redirect.log;
# 直接 return 301,不要在前面加任何 rewrite
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# HSTS:告诉浏览器以后直接用HTTPS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 如果需要把 www 跳到裸域
if ($host = 'www.example.com') {
return 301 https://example.com$request_uri;
}
# 正常的业务配置...
location / {
# your app config
}
}
排查工具速查表
| 排查步骤 | 命令/方法 |
|---|---|
| 检查Nginx收到的原始请求 | 查看access.log中$request_uri列 |
| 绕过CDN直连测试 | curl -v http://源站IP/path?参数 |
| 对比跳转前后URL | curl -vL http://域名/path?参数 2>&1 | grep Location |
| 检查CDN跳转规则 | Cloudflare: Page Rules / Always Use HTTPS |
| 验证$request_uri值 | 在return前加echo模块打印变量 |
相关文章推荐
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论