前言
很多运维朋友在配置 Nginx 永久重定向时,通常第一反应就是用 return 301。但最近遇到一个典型场景:页面做了永久重定向后,Google Analytics 的 UTM 参数全丢了,流量归因直接失效。换成 return 308 之后,问题迎刃而解。
308 和 301 都是永久重定向,但两者在参数处理上的行为有本质区别。今天这篇文章就把这个问题彻底讲清楚。
一、308 和 301 的核心区别
1.1 规范层面的定义
- 301 Moved Permanently:旧资源永久移动,POST 请求可以转为 GET(浏览器行为)
- 308 Permanent Redirect:旧资源永久移动,方法不变(POST 保持 POST)
从 RFC 7538 定义来看,308 出现得比 301 晚,就是为了解决 301 在某些场景下会把 POST 偷偷转成 GET 的问题。
1.2 实际行为对比
| 特性 | 301 | 308 |
|---|---|---|
| SEO 权重传递 | ✅ 传递 | ✅ 传递 |
| 查询参数保留 | ❌ 默认丢失 | ✅ 默认保留 |
| POST 转 GET | ⚠️ 浏览器可能转 | ✅ 不转 |
| 浏览器缓存 | ✅ 缓存 | ✅ 缓存 |
| 适用场景 | 简单页面跳转 | API / 表单 / 参数敏感场景 |
二、Nginx return 308 默认参数行为
在 Nginx 中使用 return 308 最省心的一点是:查询参数默认保留,不需要额外处理。
# 最简写法,参数自动保留
server {
listen 80;
server_name old-site.com;
return 308 https://new-site.com;
}
# 或者在 location 块中
location /old-page {
return 308 https://new-site.com;
}
访问 https://old-site.com/old-page?utm_source=google&utm_medium=cpc 会自动跳转到 https://new-site.com/old-page?utm_source=google&utm_medium=cpc,参数完整保留。
三、5个实战配置场景
场景1:基础 HTTPS 永久跳转,参数全保留
server {
listen 80;
server_name www.example.com example.com;
return 308 https://www.example.com;
}
适用于 HTTP 强制跳转到 HTTPS 的场景,UTM 参数、分页参数全部保留。
场景2:带条件的参数保留(只保留特定参数)
server {
listen 80;
server_name old.com;
# 只保留 utm_ 开头的参数,丢弃其他
if ( ~ "^/campaign\?(.*)$") {
return 308 https://new.com/campaign?;
}
return 308 https://new.com;
}
这里用了正则捕获组 手动拼接参数,需要注意用 rewrite 配合 break 也能达到同样效果。
场景3:POST 请求跳转,参数和请求体都保留
location /api/legacy {
return 308 https://new-api.com/api/v2;
}
308 保证了 POST 请求跳转到新地址后,Content-Type、请求体、方法都不变。这是它和 301 最大的使用区别。
场景4:多域名合并跳转
server {
listen 80;
server_name legacy-a.com legacy-b.com;
# 通过变量判断来源域名,在跳转目标中替换
set "new-site.com";
if (System.Management.Automation.Internal.Host.InternalHost = "legacy-a.com") {
set "new-site.com";
}
if (System.Management.Automation.Internal.Host.InternalHost = "legacy-b.com") {
set "new-site.com";
}
return 308 https://;
}
场景5:部分路径永久迁移,保留参数
location /blog/2024 {
return 308 https://www.example.com/blog;
}
这里用了 来拼接查询参数, 在没有参数时是空字符串,有参数时是 ?,自动处理了参数有无的边界情况。
四、308 vs 301 vs 307 选型决策
很多人在 301 和 308 之间纠结,其实看一个决策树就够了:
- 页面是 GET 请求(普通链接跳转)→ 优先选 301,兼容性好
- 页面有 表单 / API(POST 请求)→ 必须选 308,方法不变
- 需要 临时跳转(维护页、AB测试)→ 选 302 或 307
- 临时跳转 + POST 请求 → 必须选 307
- 跳转后 参数敏感(UTM、追踪参数)→ 选 308,参数默认保留
五、常见问题排查
5.1 跳转后参数丢失
如果用了 return 308 但参数还是丢了,检查是否在 proxy_pass 之后跳转。proxy_pass 会消耗掉原始 URI,导致 获取不到原始路径。解决方法是用 set 先保存。
5.2 出现双重问号
有些配置写成 return 308 https://new.com/?,如果原 URL 本身带参数,就会出现 ?? 的问题。用 替代固定的 ? 即可:
return 308 https://new.com; # ✅ 正确
return 308 https://new.com/?; # ❌ 双重问号
5.3 浏览器缓存导致跳转不生效
308 会被浏览器强缓存,如果修改了跳转配置后访问没反应,清一下浏览器缓存,或者用 curl -I 强制检查响应头。
六、验证命令
# 验证 308 跳转是否正确,参数是否保留
curl -I https://old-site.com/page?utm_source=google
# 查看响应头中是否有 Location 和 308 状态码
curl -I -L https://old-site.com/page?utm_source=google
# PowerShell 版本
Invoke-WebRequest -Uri 'https://old-site.com/page?utm_source=google' -MaximumRedirection 0
正常的 308 响应头应该类似:
HTTP/1.1 308 Permanent Redirect
Location: https://new-site.com/page?utm_source=google
总结
Nginx return 308 在参数保留方面比 return 301 更可靠,默认保留查询参数,不需要额外配置。对于有表单、API、UTM 追踪需求的场景,308 是更稳妥的选择。记住:简单页面用 301 没毛病,参数敏感场景优先 308。
相关文章:
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论