
最近排查一个线上问题发现有人在Nginx里把临时跳转和永久跳转混着写结果某搜索引擎的收录页面全都变成了中间跳转页权重和流量掉了一大截。这种事在站点里其实特别常见说小不小说大也大。很多人对301和302的理解停留在“反正都能跳转”的层面但这两个状态码从协议语义、浏览器行为到搜索引擎处理逻辑差异都非常大。用错了轻则多一次无效请求重则影响整站收录和排名。这篇文章就把301和302彻底拆开讲透从协议底层逻辑、实际配置写法、SEO差异到排错手段一条条捋清楚。不管是刚入门的后端开发、前端调接口的同学还是负责站点运维、SEO优化的朋友看完应该都能对这两个状态码有个完整的判断框架。1. 301和302到底差在哪从定义到使用场景先看看协议层面怎么定义这两个状态码。301 Moved Permanently含义是请求的资源已经被永久移动到新地址以后所有对该资源的请求都应该使用新的URL旧地址直接失效302 Found含义是请求的资源临时被调走了当前先用新地址响应但旧地址后续还是有效的。这段话翻译成人话就是301等于宣布“搬家了再也不回老地址”302等于“临时在隔壁办工过段时间还回来”。这个语义差异直接决定了后续所有行为。1.1 浏览器是怎么区别对待它们的浏览器收到301响应时会记住这个重定向关系。下次用户在地址栏输入旧地址浏览器可能会直接跳过请求自己跳到新地址去收302响应时则不会做这种长期记忆每次访问旧地址都会再次向服务器确认一次。这句话在排障中非常关键。我在实际项目中踩过一个坑把一个临时活动的入口用了301重定向活动结束后把跳转撤掉结果老用户访问旧链接还是被浏览器强行跳到早已下线的活动页。原因就是浏览器缓存了301清掉缓存或者换个无痕窗口才好。这是301最典型的“副作用”也是为什么临时跳转必须用302。另一个直观差异是请求方式。HTTP规范里301和302都允许客户端在重定向时把POST改为GET这就引发了不少业务事故。比如用户在A页面提交了表单后端返回302指向B页面部分浏览器会直接用GET请求B页面导致提交的幂等性无法保证。所以在处理支付回调、登录授权这类场景时很多后端接口会明确改用307或308确保POST方法在重定向过程中不被改变。关于307和308后面详细说。1.2 搜索引擎对二者的处理逻辑完全不同搜索引擎爬虫处理301时会更新索引库里的URL把旧链接的权重、收录信息全部迁移到新链接上之后爬虫会优先抓取新地址旧地址逐渐从索引中移除。这个过程在业内叫“权重转移”。做过域名迁移或者站点改版的朋友应该深有体会换完域名后如果只做302排名和收录基本会掉光如果做对了301通常两到三周搜索引擎就能把大部分权重迁移过去。302就不一样了。爬虫看到302会认为这是临时的变化不会轻易更新索引中的主URL旧地址继续保留并参与排名计算。问题在于很多站点出于运营目的把需要永久变更的页面也配成了302导致搜索引擎反复抓取旧地址每次都会多付出一次重定向请求白白消耗抓取配额还可能出现“搜索引擎收录的是中间页”这种怪象。所以在决定用哪个状态码之前先问自己一个问题旧地址是不是永远不再启用了如果是用301如果只是暂时跳开一定要用302。这个判断是所有选择的基础。1.3 从一次案例看选择错误的影响范围之前帮某内容平台做站点体检发现它的分页列表页全部用了302跳转到最新列表。结果就是搜索引擎收录的全部是带参数的URL因为302不会更新主索引页面权重迟迟无法汇聚。更麻烦的是用户点击搜索结果后进入链接又被302跳一次白白增加一次往返延迟。整站的平均响应时间从200毫秒被拉高到接近500毫秒流量损耗肉眼可见。后来把这些302全部改成301收录结构和排名在下一轮抓取周期就开始改善。这种案例在实践中非常多状态码配置不当的影响往往被低估它不只是协议层面的“小问题”而是直接关联到用户体验、站点性能、搜索流量的全局性配置。2. 重定向的底层机制响应头、方法变化与3xx家族只记住301和302两个编码还不够真正要搞懂重定向必须看懂服务器返回的响应头以及3xx这个大家族里每个成员的角色。很多人在排查问题时卡住就是因为对机制本身的认知有盲区。2.1 Location响应头是重定向的核心服务器返回301或302时必须在响应头里带上Location字段告诉客户端“要跳到哪里去”。代码层面长这样HTTP/1.1 301 Moved Permanently Location: https://example.com/new-path Content-Length: 0客户端读取到状态码和Location之后才会发起二次请求。反过来如果服务器只设置了状态码没有Location头浏览器就会停在原地用户看到的效果是页面一直白屏或者报错。这个细节在自研网关、Mock服务、后端框架的定制响应里特别容易漏。Location里的地址可以是完整的绝对URL也可以是相对路径。绝对URL兼容性最好各个代理、CDN环境下都不会出问题相对路径虽然浏览器也支持但如果经过中间层改写可能产生意外的拼接结果。我个人的习惯是后端代码里永远返回绝对URL减少歧义。2.2 Refresh头是怎么实现“延迟跳转”的除了状态码加Location还有一种常见的HTTP重定向方式是Refresh响应头长这样HTTP/1.1 200 OK Refresh: 5; urlhttps://example.com/landing它的含义是“5秒后跳转到新地址”。很多老网站会在跳转页上显示“倒计时5秒自动跳转”用的就是这个机制。Refresh和301/302最大的区别在于Refresh本身返回200状态码客户端先加载当前页面内容再根据Refresh头做跳转。所以搜索引擎看到的是两个URL都返回200对SEO来说这不是一次真正意义上的重定向权重迁移效果很差。日常实践中Refresh适合用在“告知型跳转”场景比如支付完成后的结果页提示、下载前的免责声明页。不要用它替代301/302做正规的重定向尤其不要用在需要传递权重的场景里。2.3 3xx家族全貌301/302/303/304/307/3083xx这个大类其实远不止301和302理解整个家族有助于在特殊场景里选对编码。状态码含义方法变化典型场景300Multiple Choices无服务器提示用户选择多个表示之一301Moved Permanently允许POST转GET永久迁移、域名更换302Found允许POST转GET临时跳转、活动页、登录态跳转303See Other强制转为GET表单提交成功后跳转到结果页304Not Modified无缓存协商命中本地缓存307Temporary Redirect保持原方法临时跳转且必须保留POST308Permanent Redirect保持原方法永久迁移且必须保留POST304不是传统意义上的“跳转”但它是缓存体系里的重定向行为。服务器收到条件请求后如果资源没有变化就返回304告诉浏览器“你本地缓存还能用”。很多排查问题的人会把304和302搞混其实二者逻辑完全两回事。关于303和307的出现有个历史背景早期协议对301/302的定义没有明确“POST转GET”的边界各浏览器实现不一为了拯救语义混乱后来补充了303和307。303明确要求“无论如何都转成GET”适合表单提交后的返回307明确要求“保持原方法”适合API接口的临时流转。到了今天如果你在做API网关或者前后端分离的项目建议优先用307/308来收紧行为避免不同设备上的表现不一致。2.4 链式重定向是怎么发生的重定向还有一种常见形态A返回302跳到BB又返回302跳到C最终用户请求了三四个URL才到达目标页面。这种链式重定向在日志里非常普遍原因是站点调整过程中各层配置疏于同步。链式重定向的最大代价是性能损耗。浏览器每跳一次就要重新建立一个连接如果跨域移动端网络下每次往返可能增加几百毫秒延迟搜索引擎爬虫对链式跳转尤其敏感超过一定跳数甚至直接放弃抓取。另外链条中的任何一个环节出错整个跳转就断掉。排查链式重定向的办法是逐跳查看响应。后面会讲到用curl观察完整跳转序列这是最直接的手段。3. 服务器端与前端环境下的重定向配置实操理论部分铺垫好了接下来聊实操。不同环境下配置301/302的写法和注意点差别很大我按常见的几种环境分开说。3.1 Nginx配置301与302的两种常用方式Nginx里配置重定向主要有rewrite和return两种方式。rewrite属于URL改写return则直接返回指定状态码加Location二者在语义上有细微差别。# 使用rewritepermanent表示301redirect表示302 rewrite ^/old/(.*)$ /new/$1 permanent; rewrite ^/old/(.*)$ /new/$1 redirect; # 推荐使用return语义更直接 return 301 /new/$1; return 302 /new/$1;rewrite和return的选择有个讲究rewrite会把正则匹配到的片段捕获后重新拼接适合复杂的路径改写return适合简单直接的跳转性能更高因为它不再执行后续的rewrite阶段。实际经验是能用return就用return可读性和性能都更好。还有一个容易被忽略的点Nginx里return后面跟的状态码如果是301/302Location默认会继承当前请求的Host和端口生成相对完整的绝对URL。但如果网站本身在CDN或反向代理后面外层域名和内层回源地址不一致Nginx生成的Location可能变成内网地址直接暴露给用户。解决方法是显式拼接完整URL比如return 301 $scheme://www.example.com$request_uri;3.2 Apache环境配置重定向Apache的配置风格和Nginx不同它用的是mod_alias模块里的Redirect指令。Redirect 301 /old http://www.example.com/new Redirect 302 /old http://www.example.com/tempApache的Redirect支持路径匹配配置起来比较直观但它的匹配规则是基于目录前缀的如果要做正则匹配需要用RedirectMatch或者RewriteRule这类配置部署时一定要确认模块已经加载。很多人在Apache里配301后忘记考虑HTTPS跳转结果就是HTTP和HTTPS两套逻辑各自为政用户从百度点过来是http页面域名不统一对SEO很不友好。建议在VirtualHost配置里直接把80端口的请求统一301到443端口把HTTPS跳转和业务跳转分开处理。3.3 后端代码里怎么返回301/302无论前端还是后端应用重定向的最终本质都是“设置响应状态码Location头”。以Node.js为例// Express框架写法 res.redirect(301, /new-path); res.redirect(302, /temp-path); // 原生Node.js写法 res.statusCode 301; res.setHeader(Location, /new-path); res.end();Java的Spring框架里是return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY) .location(URI.create(/new-path)) .build();Go标准库是http.Redirect(w, r, /new-path, http.StatusMovedPermanently)这些框架提供的接口本质都一样只是封装层数不同。需要注意的自定义场景是如果你在框架里做了全局的响应拦截器有些拦截器会把Location头吞掉或者改写导致重定向失效。排查这类问题时不要只盯着路由代码把过滤器、中间件全部过一遍。3.4 前端能做的重定向方式与限制前端场景常见的重定向有meta refresh和JavaScript跳转。!-- meta refresh 写法 -- meta http-equivrefresh content0; url/new-path// JavaScript 跳转写法 window.location.replace(/new-path);这两种方式在HTTP协议层面返回200浏览器根据页面内容再发起跳转。它们的使用限制很明显爬虫看到200状态码会认为自己访问的就是有效页面不会进行权重转移而且每次爬取都要重新加载一遍页面再跳转。所以前端方式只适合交互型跳转比如登录后跳回原页面、多步表单的条件路由。凡是涉及站点结构变更、URL永久调整的都应该走服务器端的301或302。有个细节想通过前端方式模拟“重定向后不留历史记录”的需求很常见。window.location.replace要优于location.href因为replace会替换当前历史记录用户点返回不会回到中间跳转页体验上更接近服务器端重定向的效果。4. 重定向的排查手段与问题纠错理论知识再扎实到了线上排查还是得靠工具说话。这一节我把平时用顺手的排障手段、常见问题和应对思路整理出来。4.1 用curl快速查看重定向全链路curl是排查重定向问题的首选工具参数-I可以只看响应头。curl -I -L --max-redirs 5 https://example.com/old-path-I发出HEAD请求查看响应头-L表示跟随重定向--max-redirs限制最大跳转次数。输出里会出现多段响应头每一段都可以看到状态码和Location整条重定向链一目了然。如果不加-Lcurl只返回第一跳的状态码和Location不继续跟下去。在排查具体问题时我通常会分两步走第一次不加-L单独看第一跳返回的是301还是302、Location指向哪里第二次加-L把整条链串起来看有没有循环或多余跳数。看到301还要特别留意响应的Cache-Control头如果是301且被CDN缓存了旧地址的跳转关系会持续很长时间对业务调整很不友好。4.2 Chrome DevTools里的重定向观察技巧Chrome开发者工具里Network面板默认会把重定向过程简化显示。想看到完整链路可以打开面板后勾选“Preserve log”然后重新加载页面列表里会出现多条同名请求每条对应一跳可以展开查看各自的响应头和状态码。这里的额外价值在于能看到请求的实际耗时分布。一条重定向如果跨域往往意味着多一次DNS解析、TLS握手和连接建立。从Network面板就能直观看到这些开销帮助你判断是否可以把跳转链压缩掉一层。4.3 配置了301却出现死循环怎么办死循环是重定向最常见的故障。现象是浏览器报ERR_TOO_MANY_REDIRECTS或者curl跟随跳转时超过最大次数强制终止。排查这种问题有个固定套路用curl -I不带-L看第一跳返回什么状态码、Location指向哪里。对比Location和原始URL检查是否指向了同一个地址或者互相指来指去。检查Web服务器配置里是否存在多条重定向规则规则的生效顺序可能和预期不符。查看域名解析层有没有额外跳转比如CDN回源规则里配置了强制HTTP跳HTTPS源站又配了强制HTTPS跳HTTP两方逻辑冲突。留意环境变量比如X-Forwarded-Proto或X-Forwarded-Port在代理层没有正确传递后端判断协议时拿到的总是http导致不断的跳转。第五点在实际项目中很常见。应用层判断“当前是不是HTTPS”走的是代理传过来的X-Forwarded-Proto头如果代理没配这个头应用永远认为自己处在HTTP环境于是拼命301到HTTPS而代理层看到的已经是HTTPS请求又不动最终就陷入循环。解决思路是在代理层透传或者改写这类头让源站拿到正确的协议信息。4.4 301和302的缓存策略与排查很多人不知道301和302的缓存行为也可以控制。协议层面上301默认可以被浏览器和CDN缓存302默认不会。但你可以通过响应头显式控制Cache-Control: no-cache, no-store, must-revalidate在返回302响应时加上这段头可以避免某些代理服务器对302做激进缓存。在返回301时如果希望尽快生效而非等缓存过期同样可以通过设置no-store来阻止中间层缓存。实际排障时遇到“改了重定向地址但线上还是跳旧地址”大概率就是缓存问题。排查顺序是先看浏览器是否命中本地缓存再看CDN层面是否有缓存最后查服务器配置是否确实更新了。用curl查看响应头里的Cache-Control和Age字段能快速判断缓存来自哪一层。5. 我踩过的那些重定向坑与防范心得最后分享几个真实案例都是我在不同项目里踩过的坑。没有亲身经历过的话看文档很难意识到这些细节有多重要。第一个坑是登录态跳转被缓存。某系统登录成功后跳转到用户中心后端返回的302被一层代理服务器强缓存了导致用户A登录成功后用户B打开同一URL直接被跳转到A的首页数据。排查到最后发现是代理层对302做了缓存而且没有Key区分用户。从那以后我要求所有涉及用户态的302响应必须带Cache-Control: no-store从协议层杜绝这类事故。第二个坑是HTTP与HTTPS之间的跳转链设计不当。有个站点在DNS解析层做了HTTP到HTTPS的301CDN回源时又把HTTPS落到一个HTTP的源站源站自身又配了301到HTTPS。用户访问一次页面实际经过了三次跳转DNS层一次、CDN层一次、源站一次。响应时间从80毫秒涨到了600多毫秒。后来重新梳理把跳转职责收敛到CDN边缘层和源站层各自只做一次决定整条链路才清爽。第三个坑是302导致的搜索引擎索引混乱。某商城为了做促销把详情页全部302到活动页持续了一个多月。活动结束后搜索流量骤降因为搜索引擎在这段时间里建立的大量索引都是活动页URL活动页下线后这些URL全部404。如果当时用的是301索引会稳定迁移到活动页即使活动结束后也有机会通过301把权重转移到下一个承接页面。促销逻辑和SEO逻辑经常打架但技术侧至少要把选择权透明化让业务明白风险。第四个坑是移动端和桌面端分别跳转时状态码用反。有些站点桌面版是完整内容移动版是简化页面通过检测User-Agent做302跳转这是合理场景。但有人为了“省事”直接给移动端配置成301后果是用户手机浏览器永久记住了移动版URL之后即使用户想切换桌面版网页浏览器还是会直接跳到移动版。移动适配跳转必须用302这已经是行业共识但仍然有站点在犯。说到这我个人的习惯可以总结成几句话凡是确认永久变更的比如换域名、改路径、统一协议一律301同时确认不经过浏览器缓存的历史残留凡是临时性流转的比如活动页、登录跳转、移动适配、A/B测试一律302并显式关闭缓存涉及POST接口的重定向优先307和308所有重定向配置上线前用curl走一遍完整跳转链记录状态码与耗时。这几点做到位重定向相关的坑基本都能避开。状态码选错不会立刻让网站挂掉但会在流量、收录、性能上持续放血越早纠正越省事。