0

Nginx return 301 $request_uri参数丢失排查:3个隐藏陷阱和修复方法

2026.05.27 | youres | 8次围观

前言

很多运维同学在配置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?参数
对比跳转前后URLcurl -vL http://域名/path?参数 2>&1 | grep Location
检查CDN跳转规则Cloudflare: Page Rules / Always Use HTTPS
验证$request_uri值在return前加echo模块打印变量

相关文章推荐

版权声明

本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论