重定向看起来是个小问题,但它是很多站点 SEO 出问题的隐形源头。站点换域名、加不加 www、http 跳 https、栏目改路径、文章合并删除——每一次 URL 变动如果没有正确处理,就会留下一堆 404 或者重复内容,轻则权重分散,重则被搜索引擎判定为作弊。这篇文章把 Nginx 下最常见的重定向场景一次讲清楚,包括 301 与 302 的区别、return 与 rewrite 的选用、以及怎么用一套配置把域名规范化做到滴水不漏。
一、先搞懂 301、302、307、308
重定向的状态码决定了搜索引擎如何处理链接的权重,选错会直接损失排名。
- 301 Moved Permanently(永久重定向):告诉搜索引擎"这个 URL 永远不会再用了,请把权重全部转移到新地址"。换域名、http→https、统一 www 前缀,全部用 301。
- 302 Found(临时重定向):表示暂时跳转,原 URL 以后还会用。搜索引擎不会转移权重,会继续保留原 URL。适合 A/B 测试、临时维护页。注意:
rewrite ... permanent是 301,redirect才是 302,很多人写反了。 - 307 / 308:HTTP/1.1 引入,分别是 302/301 的"严格版",区别在于 307/308 会保留原始请求方法(POST 还是 POST),而 301/302 在部分老客户端里会把 POST 变成 GET。涉及表单提交或 API 跳转时要留意这一点。
一句话总结:SEO 场景下,只要确定这个 URL 以后不再使用,就用 301;不确定就用 302。千万不要为了"保险"给所有跳转都用 302,那等于把权重送给旧地址。
二、return 和 rewrite 该用哪个
Nginx 提供了两种实现跳转的指令,性能差一个数量级:
# 推荐:return,在处理阶段早期直接返回,不经过正则匹配和 location 重新查找 return 301 https://www.example.com$request_uri; # 次选:rewrite,需要做正则捕获替换时才用 rewrite ^/old-path/(.*)$ /new-path/$1 permanent;
结论很明确:能用 return 就用 return,只有需要正则表达式捕获和替换路径时才用 rewrite。 return 属于 rewrite 模块的早期处理阶段,开销极低;rewrite 会对每条请求做正则匹配,并且可能触发 location 重新查找(如果加上了 last 标志),在流量大的站点上差别可观。
三、场景一:统一 www 与非 www
搜索引擎会把 example.com 和 www.example.com 当成两个不同的站点,指向同一份内容就是重复内容,权重被一分为二。必须选一个作为主域名,另一个 301 过去。个人站长一般推荐带 www,因为 www 的子域名可以单独配置 CDN 和 CNAME,将来做多级域名也更灵活;也有人偏好裸域名更简洁。选哪个不重要,重要的是全站统一。
假设主域名是 www.example.com,把裸域名跳过去:
server {
listen 80;
listen [::]:80;
server_name example.com;
return 301 https://www.example.com$request_uri;
}如果反过来,主域名是不带 www 的,就写成:
server {
listen 80;
listen [::]:80;
server_name www.example.com;
return 301 https://example.com$request_uri;
}关键点:$request_uri 会带上原始路径和查询字符串,比如访问 example.com/post/123?from=wx 会跳到 www.example.com/post/123?from=wx,不会丢掉路径。这是最容易被写错的地方——只写 return 301 https://www.example.com; 会把所有页面都跳到首页,等于自己制造一堆软 404。
四、场景二:http 全站跳 https
部署完 SSL 证书之后,必须把所有 http 请求 301 到 https,否则用户还能用明文访问,浏览器会提示"不安全",SEO 上也是两个版本。标准写法:
server {
listen 80;
listen [::]:80;
server_name www.example.com example.com;
# 保留 Let's Encrypt 的验证目录,否则证书续期会失败
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
allow all;
}
location / {
return 301 https://www.example.com$request_uri;
}
}⚠️ 这里有个非常经典的坑:全站 301 之后忘记放行 /.well-known/acme-challenge/,导致 Let's Encrypt 的 http-01 验证请求也被跳走,证书自动续期失败。90 天后证书过期,全站报错。上面那段 location 就是为这个准备的,用 ^~ 前缀匹配优先于普通 location,确保验证文件能正常返回。
HTTPS 站点本身还要加上 HSTS 响应头,让浏览器以后强制走 https。但 HSTS 一旦下发就不能轻易撤销,建议先设一段短时间观察:
add_header Strict-Transport-Security "max-age=300" always; # 稳定运行一段时间后再改成 max-age=31536000
五、场景三:换域名(整站迁移)
换域名是最需要小心的操作,处理不好等于把几年积累的权重全部清零。正确流程:
# 老域名的 server 块全部 301 到新域名
server {
listen 80;
listen [::]:80;
server_name old-domain.com www.old-domain.com;
return 301 https://www.new-domain.com$request_uri;
}
server {
listen 443 ssl http2;
server_name old-domain.com www.old-domain.com;
ssl_certificate /etc/letsencrypt/live/old-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/old-domain.com/privkey.pem;
return 301 https://www.new-domain.com$request_uri;
}配套要做的事情:
- 保留老域名的 SSL 证书至少一年,否则用户访问老链接会看到证书错误。老域名续签的钱不能省。
- 程序内部也要改。WordPress 要在数据库里替换站点地址,Typecho 要改配置里的 siteUrl,静态站要重新生成绝对链接。只做 Nginx 跳转但页面里的链接还指向老域名,会形成"跳过去又被跳回来"的循环。
- 搜索结果里更新:百度资源平台、Google Search Console 都提供"站点改版/地址变更"工具,提交后能加速权重转移。
- 老域名的 sitemap 暂时保留,里面仍列老 URL,配合 301 让爬虫尽快发现新地址。
六、场景四:单页改路径与文章合并
栏目结构调整、文章 slug 修改、几篇文章合并成一篇,都要补上单条 301:
# 旧栏目改名
rewrite ^/tech/(.*)$ /linux/$1 permanent;
# 单篇文章换路径
location = /old-article.html {
return 301 https://www.example.com/new-article.html;
}
# 多篇合并到一篇(用 map 批量处理更清晰)
map $request_uri $redirect_target {
default "";
/article-a.html https://www.example.com/merged.html;
/article-b.html https://www.example.com/merged.html;
/article-c.html https://www.example.com/merged.html;
}
server {
# ... 其他配置
if ($redirect_target != "") {
return 301 $redirect_target;
}
}用 map 的好处是把跳转规则集中在一处,几十上百条也不混乱,而且 map 只在请求进入时求值一次,性能优于一堆 rewrite。⚠️ 注意 if 在 location 之外使用是安全的(也就是所谓的"if is evil"只在 location 内部成立),上面这种写法是官方认可的用法。
七、验证与自查清单
配置改完之后,不要只靠浏览器访问一次就算完,用 curl 精确验证状态码:
# 只输出状态码和 Location 头
curl -sI -o /dev/null -w "%{http_code} %{redirect_url}\n" http://example.com/post/123
# 检查跳转链是否只有一跳(不要出现 301 → 301 → 200)
curl -sIL -w "\n最终: %{url_effective} 状态: %{http_code} 跳转次数: %{num_redirects}\n" \
-o /dev/null http://example.com/自查清单:
- http 和 https、带 www 和不带 www 的四种组合,最终都收敛到同一个规范 URL。
- 跳转最多一轮完成,不要出现链式跳转,链式跳转会拖慢爬虫抓取并稀释权重。
- 跳转目标带上原始路径(
$request_uri),不要一律跳首页。 - 404 页面返回的是真正的 404 状态码,不是 200。很多程序把 404 页渲染成 200,搜索引擎会认为是正常页面,导致大量无效页面被收录。
- 用
nginx -t检查语法,reload 之前务必确认没有拼写错误,尤其是$request_uri后的分号。
八、带参数的动态跳转与查询字符串处理
真实站点里,很多 URL 是带参数的:/search?q=nginx&page=2、/index.php?post=123。跳转时如果处理不当,参数会被丢掉或者出现双重编码。先看几个实用写法。
想把 example.com/index.php?post=123 这种老式动态地址改写成伪静态地址,可以用 return 加正则捕获:
location ~ ^/index\.php$ {
if ($arg_post != "") {
return 301 https://www.example.com/post/$arg_post.html;
}
}
# 也可以用 rewrite 一句话搞定
rewrite ^/index\.php\?post=(\d+)$ /post/$1.html? permanent;注意 rewrite 的替换目标末尾加一个 ?,作用是丢弃原始查询字符串,否则 Nginx 默认会把老参数拼到新 URL 后面,跳成 /post/123.html?post=123,虽然不算错误,但会产生一堆带参数的重复 URL 被搜索引擎收录。这个 ? 是很多人不知道的小技巧,需要保留参数时就不加。
如果只是想去掉 URL 里的 utm 等跟踪参数、统一成干净地址,可以这样写:
if ($args ~ "^(.*&)?(utm_[a-z]+|from|spm)=[^&]*(&.*)?$") {
return 301 $scheme://$host$uri;
}⚠️ 这类涉及参数的判断一定要先在测试环境用 curl -sI 反复验证,避免写出死循环——把 A 跳到 B、B 又跳回 A,浏览器会直接报"重定向次数过多"。
九、用日志验证跳转是否真的生效
跳转配置上线之后,除了人工访问,还可以从日志里确认搜索引擎和真实用户是否都正常跳转。Nginx 日志里 301/302 的状态码会如实记录:
# 统计各状态码数量,看跳转占比是否正常
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# 找出所有被跳转的 URL,确认没有遗漏或误伤
awk '$9 == 301 || $9 == 302 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -30
# 检查有没有"跳转后再 404"的坏链
grep -E '" 40[34] ' /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head如果日志里出现大量 404,通常是旧链接既没被重定向、目标页也不存在;如果出现大量 301 但对应的目标页访问量很低,说明跳转链过长或者跳到了错误的地方。有条件的话接上搜索蜘蛛的日志,单独看百度、Google 的抓取状态,确保它们拿到的也是 301 而不是超时或 404。
十、小结
重定向的核心就三句话:该永久的用 301,能用 return 就不用 rewrite,跳转永远带上原始路径。把域名规范化(www、https)这两条基础跳转配好,能挡掉一大半重复内容问题;换域名和改路径时补上对应的 301,并给足够长的过渡期,权重才不会白丢。这些配置一共不到二十行,但对 SEO 的影响远大于写十篇新文章。
最后提醒一句:每次修改 Nginx 配置都要先备份原文件,改完 nginx -t 验证通过再 reload,出问题能一分钟内回滚。这是运维的基本习惯,也是少熬夜的关键。