写代码和看代码的人每天都和状态码打交道,但多数人对它们的理解停留在 "4xx 客户端错、5xx 服务端错" 这个颗粒度。真实场景里,301 和 302 的差别能让你的域名被搜索引擎降权,401 和 403 的差别决定了你要去查登录还是查权限,500 和 502 的差别决定了该看应用日志还是网关日志------每一个错判都会把排查方向带偏半小时。
这篇文章把最常出错的几组状态码成对拆开讲,最后用 curl 实际看一遍。难度不高,但都是面试和实际排查里真实会踩的坑。
1 重定向:301/302 差的不是数字,是 "多久"
1.1 先看两个的语义
301Moved Permanently 是永久重定向。它告诉浏览器和搜索引擎两件事:资源永久搬家了,请更新你的记录。浏览器会把这个跳转 aggressively 地缓存下来,之后用户再访问旧 URL,浏览器直接去新地址,连旧服务器都不问。
302Found 是临时重定向。语义是 "现在临时用另一个地址,原地址还有效"。浏览器默认不缓存 302,搜索引擎也不会把原地址的权重转移走。
这个差别在实际项目里的分量比看起来大。举个真实场景:某次活动要临时把首页导到一个营销页,有人图省事配了 301。活动结束后把重定向撤掉,结果已经访问过的用户浏览器里那条 301 还缓存着------301 是被浏览器长期记住的,用户清理缓存之前会一直跳到营销页。反过来,永久迁移域名时误用 302,搜索引擎就不会把旧域名的权重转给新域名,收录量慢慢掉下去。
一句话记法:301 是搬家,302 是出差。搬家要通知所有人改地址,出差只是暂住。
1.2 301/302 的隐藏坑:方法可能被改掉
历史遗留问题:按早期实现,收到 301 或 302 后,浏览器会把 POST 请求改成 GET 再去请求新地址。对网页跳转无所谓,但对 API 是灾难------你 POST 到 /api/upload,网关回了个 301,浏览器转头用 GET 去打新地址,语义完全变了。
为修这个问题,后来加了两个更严格的状态码:
- **307 Temporary Redirect:**语义同 302,但明确要求保留原请求方法,POST 跳过去还是 POST;
- **308 Permanent Redirect:**语义同 301,同样保留方法。
给 API 做重定向时优先选 307/308;给网页做跳转,301/302 够用。四个码一张表对比:
| 状态码 | 语义 | 浏览器缓存 | POST 是否保持 |
|---|---|---|---|
| 301 | 永久 | 会 | 可能变 GET |
| 302 | 临时 | 默认不 | 可能变 GET |
| 307 | 临时 | 默认不 | 保持 POST |
| 308 | 永久 | 会 | 保持 POST |
2 304:不是错误,也不是普通的成功
304 Not Modified 是被误解最多的状态码。它不是 "资源没了",也不是 "资源正常返回",而是协商缓存命中时服务端的回答:你本地缓存的那份还是最新的,直接用,我不再传一遍了。
2.1 协商缓存的完整流程
浏览器缓存分两级:强缓存和协商缓存。强缓存靠 Cache-Control 的 max-age 控制,有效期内浏览器根本不发请求,所以强缓存命中时看不到 304------304 只出现在协商缓存这一级。
流程走一遍:
第一次请求时,服务端返回资源本体,同时在响应头里带两个标识之一:
bash
ETag: "abc123"
Last-Modified: Mon, 09 Jun 2026 09:00:00 GMT
强缓存过期后,浏览器再次请求这个资源,会在请求头里把之前的标识回传给服务端:
bash
If-None-Match: "abc123" ← 对应上次的 ETag
If-Modified-Since: Mon, 09 Jun 2026 09:00:00 GMT ← 对应上次的 Last-Modified
服务端拿这两个值和当前资源比对:没变就回 304(不带响应体),变了就回 200 加新的资源。注意浏览器是先检查 If-None-Match,匹配就直接 304;不匹配再看 If-Modified-Since。

2.2 ETag 和 Last-Modified 为什么有两个
Last-Modified 的精度只到秒,一秒内改两次它识别不出来;文件内容没变但时间戳变了(比如重新部署),它又会误判为已修改。ETag 是按内容生成的指纹,内容变它才变,精度更高,所以两者并存时 ETag 优先。
2.3 用 curl 验证一次
找个静态资源实测:
bash
# 第一次,拿到 ETag
curl -I https://example.com/style.css
# 响应头里会有:ETag: "xxxxx"
# 第二次,把 ETag 带回去
curl -I -H 'If-None-Match: "xxxxx"' https://example.com/style.css
# 这次返回的就是 304 Not Modified
对着自己项目跑一遍,比背十遍流程图记得牢。顺带验证一个高频面试题的准确说法:304 的响应没有响应体,但状态行和响应头都在;浏览器拿本地缓存的 body 去渲染,所以 DevTools 里看到内容正常显示不代表 304 传了完整资源。
3 401 和 403:一个查证件,一个查票
这两个是 REST 接口里最常混用的状态码,也是面试爱问的区分题。
401Unauthorized 对应的是认证:你是谁还没确认。请求没带凭证、token 过期、密码错误,服务端无法识别身份,都回 401。响应头里通常带 WWW-Authenticate,提示客户端该用什么方式认证。
403Forbidden 对应的是授权:知道你是谁了,但你没权限做这件事。普通用户去调管理员接口、账号被封禁、IP 在黑名单里,回 403。
一个类比:坐飞机,401 是安检让你出示证件而你拿不出来,403 是证件没问题但你没有这张机票。两者的排查方向完全不同------401 去查登录态和凭证链路,403 去查权限配置和角色划分。
后端代码里一个常见错误是权限不足时回 401。这会误导客户端去刷新 token 或跳登录页,但用户登录一百次也解决不了权限问题。按语义写:token 无效 401,权限不足 403,前端才能给出正确的处理动作。
顺带提 404:它的语义是资源不存在,但实际项目里 404 不一定是路径真不存在------路由没注册、Nginx location 没配到、请求被网关拦下来都可能回 404。排查时别只看状态码就下结论。
4 5xx:500/502/504 的责任边界
这三个都叫 "服务器错误",但出问题的位置不一样。区分的关键是问一句:这个状态码是谁发出来的。
500Internal Server Error 是应用自己发的。请求已经进了你的服务,处理过程中出了异常------空指针、panic、配置缺失------应用兜不住,回 500。排查去翻应用的错误日志,一定有对应的异常栈。
502Bad Gateway 是网关发的。网关(Nginx、负载均衡器)替用户去请求你的服务,结果服务侧连接被拒绝、进程崩了、返回了不合法的响应,网关只好回 502。典型场景:容器刚启动端口还没 ready、上游服务挂了。排查去翻网关的 error.log,找 connect() failed 或 upstream prematurely closed 这类记录;后端日志里可能什么都没有,因为请求根本没到它手上。
504Gateway Timeout 也是网关发的,含义是 "上游还活着,但太慢了"。网关等了 timeout 设的时长没等到响应,放弃并回 504。典型场景:慢 SQL、线程池打满、下游级联调用拖垮。排查看网关的响应时间是否约等于 timeout 值、上游的慢查询日志。
三者的排查起点一句话概括:500 看应用日志,502 看网关日志和上游进程状态,504 看上游性能和超时配置。这条责任边界在排障时能直接省掉一半的瞎猜时间。

5 用 curl 把状态码看清楚
排查时浏览器 DevTools 有时不方便(跳转太快看不到中间步骤),命令行 curl 更直接。几个常用姿势:
bash
# 看一个 URL 的响应头(HEAD 请求,不下 body)
curl -I https://example.com
# 跟着重定向一路走到底,每一跳的响应头都打出来
curl -IL https://example.com
curl -IL 用来排查跳转链特别顺手。比如怀疑某个域名跳转次数太多,一条命令就能看到从第一跳到最终页每一站的状态码、Location、ETag,中间的 301 还是 302、链路是不是绕了远路都一目了然。
一个细节值得留意:curl -I 发的是 HEAD 请求,个别配置不当的服务对 HEAD 和 GET 的处理不一样,如果结果可疑,换 curl -sS -D - -o /dev/null 用真实 GET 只看头。
6 几个容易答错的点
最后把容易被面试官追问、也容易在实操里想当然的点集中列一下:
- **304 是不是错误?**不是。它是协商缓存命中的成功响应,不是失败也不是 "资源为空"。
- **强缓存命中时会不会出现 304?**不会。强缓存有效期内浏览器根本不发请求,304 只在协商缓存阶段出现。
- **301 和 302 能不能混着用?**能跑,但代价真实存在:301 被浏览器长期缓存且转移搜索引擎权重,误用临时跳转会造成难回滚的缓存污染;误用 302 做永久迁移会丢权重。
- **API 重定向选哪个?**涉及 POST/PUT/DELETE 的,用 307/308 保证方法不变。
- **401 和 403 谁先判断?**先认证后授权。身份没确认之前谈不上权限,所以顺序固定。
- **502 和 504 都是网关发的,怎么分?**502 是上游给的响应不合法或连不上,504 是等不到响应。前者查连接和进程,后者查性能和超时。
状态码这一层的知识不算深,但它的特点是每一条都对实际排查有直接指向性。把这些成对的区分记牢,下次线上出问题时,看一眼状态码就知道该往哪个日志文件里翻------这大概就是这类基础知识真正的用法。