文章指出作者在将网站迁移至HTTPS并启用HSTS后,遭遇收录权重不涨的问题。通过排查发现,问题源于Nginx配置中307内部伪重定向导致的爬虫抓取失败,以及反代架构下Canonical协议错配形成的收录死锁。作者通过直接返回301重定向并修正协议标签解决了问题。
上个月把手头两个流量站全量切了 HTTPS,顺手在 Nginx 加上了 HSTS(HTTP 严格传输安全头)。原本按照搜索引擎官方的改版指引,全站 HTTPS 能拿到轻微的安全性加权,新旧协议平稳过渡后收录应该稳中有升。改版两周后打开百度搜索资源平台和 Google Search Console,抓取频次断崖下跌,新文章收录从每天几十篇掉到零星几篇,后台索引页面全是“重定向错误”和“排除网页”。
在排查日志抓包两宿之后,我发现自己在 Nginx 配置、缓存状态和爬虫协议协商上踩了一连串隐蔽的坑。有些问题在桌面 Chrome 里肉眼看着完全正常,但在搜索引擎抓取机制下却全是致命漏洞。
坑点一:Chrome 里的 307 根本不是服务端返回的,源站其实一直在裸奔 302
这是最容易让人产生“配置已经完美生效”错觉的地方。
改版当天,我在 Chrome 浏览器输入 http://www.domain.com 测试。按 F12 打开 Network 面板,第一行清清楚楚显示状态码 307 Internal Redirect,Location 头指向 https://www.domain.com,随后页面顺滑加载出 HTTPS 版本。我当时想都没想,以为 Nginx 的跳转已经起效。
直到两周后分析爬虫访问日志,我才发现百度蜘蛛(Baiduspider)和 Googlebot 在访问 80 端口时,日志里赫然记录着 302 Moved Temporarily。
116.179.32.X - - [10/Oct/2026:03:14:22 +0800] "GET /article/1082.html HTTP/1.1" 302 154 "-" "Baiduspider/2.0"
为什么浏览器看到 307,爬虫看到的却是 302?
因为 Chrome 显示的 307 Internal Redirect 是浏览器本地行为。如果之前通过响应头或者预加载列表记住了该域名的 HSTS 规则,浏览器会在发出真实网络请求前,直接在内存把 http:// 改写成 https://。这次“跳转”根本没走网卡,请求压根没到达 Nginx。
而搜索引擎的抓取爬虫是无状态的分布式客户端,每次抓取任务都不会持久化浏览器的 HSTS 状态。爬虫向 80 端口发起请求时,Nginx 真实吐出的状态码是之前的遗留配置:
# ❌ 随手写出来的危险配置:默认是 302 临时重定向
server {
listen 80;
server_name www.domain.com domain.com;
rewrite ^(.*)$ https://$host$1 redirect;
}
Nginx 语法里,rewrite ... redirect; 对应的是 302,rewrite ... permanent; 才是 301。
对搜索引擎而言,302 是“临时变动”,权重不会传递给 HTTPS 新 URL,旧的 HTTP 索引必须保留。爬虫认为 HTTP 依然是主版本,反复去爬 80 端口;但每次去爬又碰到 302,抓取配额在无休止的临时跳转中被白白耗尽。
必须用裸 curl 脱离浏览器缓存复现真实网络请求:
curl -IL -A "Baiduspider" http://www.domain.com/article/1082.html
排查出来的修复方案很简单,弃用容易写错的 rewrite,直接在 80 端口显式返回 301:
# ✅ 正确做法:直接使用 return 301 且保留原始请求参数
server {
listen 80;
listen [::]:80;
server_name www.domain.com domain.com;
# 遇到 Let's Encrypt 证书续期目录需要放行
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
坑点二:反代架构下 Canonical 协议错配,形成收录死锁
很多中大型站点或者使用云服务的架构,前面都挂着 CDN 或者负载均衡(SLB)做 SSL 证书卸载,回源到后端 Nginx 或者应用服务(PHP-FPM、Node.js、Go)时走的是纯 HTTP。
改完 HTTPS 后,网页源码里大多会有规范网址标签:
<link rel="canonical" href="http://www.domain.com/article/1082.html" />
仔细看这个 href,协议居然依然是 http://。
后端的 CMS 框架在拼接页面绝对地址时,通常依赖 $_SERVER['HTTPS'] 或者请求头里的协议标识。由于 SLB 卸载了证书,后端服务收到的 TCP 请求来自内网 80 端口,程序默认当前请求是 HTTP,于是吐出了带有 http:// 前缀的 Canonical URL。
这在搜索引擎眼里形成了一个逻辑死循环:
- 爬虫访问
https://www.domain.com/article/1082.html(状态码 200)。 - 爬虫解析 HTML,看到 Canonical 标签明确声明:“本页面的规范母版本是
http://www.domain.com/article/1082.html”。 - 爬虫遵从指令,跑去抓取 HTTP 版本。
- 80 端口返回 301,强跳回 HTTPS 版本。
- 爬虫再次拿到 HTTPS 页面,Canonical 又指回 HTTP。
几个来回之后,爬虫算法直接把这类页面判定为“规范网址配置混乱”,不仅 HTTPS 页面无法建库,旧 HTTP 页面的权重也被冻结。
解决这个问题必须把真实协议头一路透传到底。
在 Nginx 的反向代理层或 fastcgi 传递处,确保传入正确的环境变量:
# 如果 Nginx 本身处理 SSL
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# 核心:必须显式告诉后端当前已启用 HTTPS
fastcgi_param HTTPS on;
fastcgi_param HTTP_X_FORWARDED_PROTO https;
}
# 如果是反向代理到 Node / Python / Go 后端
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
改完配置后,用 curl 抓取 HTTPS 页面源码过滤检查,确保整站源码里的 canonical、og:url、结构化数据(JSON-LD)里的 @id 全面切换为 https://。
坑点三:HTTP 混合内容(Mixed Content)导致爬虫无头浏览器放弃渲染
现在的搜索引擎早已不是纯文本爬虫。Googlebot 和百度新版抓取核心都使用无头浏览器(Headless Chromium)执行 JavaScript 并构建完整渲染树。
全站切 HTTPS 后,主文档虽然是 HTTPS,但很多建站时间较长的老文章里,配图、音频、甚至排版引用的第三方公共库外链依然是硬编码的 http://。
在现代 Chromium 内核中,对于主域为 HTTPS 的页面:
- 被动混合内容(Passive Mixed Content,如 img、audio):浏览器会尝试自动升级到 HTTPS,如果目标图片源服务器不支持 HTTPS,图片直接加载失败破图;
- 主动混合内容(Active Mixed Content,如 script、link css、iframe):浏览器直接强行拦截阻断,控制台报
Mixed Content: The page at 'https://...' was loaded over HTTPS, but requested an insecure script 'http://...'。
如果网页依赖的富交互脚本、目录生成脚本或者分页异步加载模块正好引用了 HTTP 资源,无头浏览器执行阶段就会直接抛出安全异常中止脚本。结果就是爬虫抓到的快照内容残缺不全,核心正文或者内链根本没有挂载到 DOM 树上。
更直接的损失是图片搜索。很多站长发现网站文字收录恢复了,但图片搜索流量归零,就是因为历史文章里的 <img src="http://..."> 触发了跨源与混合内容限制。
排查与修复分三步走:
第一步,在 Nginx 的 HTTPS 虚拟主机添加升级响应头,让支持该特性的现代客户端自动尝试 HTTPS 请求:
add_header Content-Security-Policy "upgrade-insecure-requests" always;
第二步,排查历史数据库,用 SQL 语句把正文里存量的绝对路径做替换。以常见内容系统为例:
UPDATE blog_article SET content = REPLACE(content, 'http://www.domain.com/wp-content/', 'https://www.domain.com/wp-content/');
UPDATE blog_article SET content = REPLACE(content, 'http://static.domain.com/', 'https://static.domain.com/');
第三步,站内引用静态资源尽量使用根相对路径(如 /assets/css/app.css),外部资源如果对方同时支持双协议,优先填补完整 https://,慎用过时的 // 相对协议。
坑点四:HSTS 配置踩死 includeSubDomains,误伤未配证书的私有子域
很多人改完 HTTPS 之后,去各种安全评分工具跑分,为了拿到 A+ 评级,毫不犹豫地在 Nginx 加上一串大而全的配置:
# ❌ 没想清楚就复制粘贴的高危配置
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
这行配置的意思是:通知所有访问过本站的浏览器,在未来两年(63072000 秒)内,本域名以及所有子域名必须强制使用 HTTPS 访问,并且申请加入 Chrome 的内嵌预加载列表。
坑就出在 includeSubDomains 和 preload 上。
主站虽然配齐了 SSL 证书,但一个运行多年的老域名下,往往挂着各种历史遗留服务:某个运维跑在特定端口的监控看板、某个临时调试用的内网测试接口 api-test.domain.com、甚至是没申请证书的内部 Git 仓库。
一旦访客或者站长本人访问了带有该头的主站,浏览器就记住了整条主域的 HSTS。此后再去打开那些仅支持 HTTP 的内部子域名时,浏览器直接拒绝发起 HTTP 连接,强制改走 443 端口,当场抛出 ERR_CONNECTION_REFUSED 或 SSL_ERROR_RX_RECORD_TOO_LONG。
更麻烦的是 preload。一旦提交审核进入 Chromium 源码硬编码列表,即便事后从 Nginx 里删掉这行响应头,已经发版的浏览器也不会撤销限制。想要移出列表,需要走漫长的申请和浏览器版本迭代周期。
稳妥的上线节奏应当分阶梯推进:
初期上线时,先设置较短的过期时间,且不带子域名和预加载参数:
# 第一阶段测试(有效期 1 天,验证主站功能)
add_header Strict-Transport-Security "max-age=86400" always;
观察一到两周,排查子域名和移动端爬虫抓取情况。如果确认没有异常,再平滑增加到标准时长:
# 第二阶段稳定(有效期半年)
add_header Strict-Transport-Security "max-age=15768000" always;
只有在全站所有子域名均已稳固部署证书的前提下,才去考虑增加 includeSubDomains。
生产级 Nginx 避坑配置参考
综合上述几个排坑点,给出一份兼顾技术 SEO 与安全合规的完整 Nginx 站点配置:
# HTTP 80 端口统一 301 重定向
server {
listen 80;
listen [::]:80;
server_name domain.com www.domain.com;
# Certbot 挑战目录放行
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/certbot;
}
location / {
return 301 https://www.domain.com$request_uri;
}
}
# HTTPS 443 端口主站服务
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name www.domain.com;
ssl_certificate /etc/letsencrypt/live/domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# 根域名 301 规范化到 www(按站长实际规范需求选择)
# HSTS 头:切记带 always 参数,确保 4xx/5xx 错误页也能吐出响应头
add_header Strict-Transport-Security "max-age=15768000" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Content-Security-Policy "upgrade-insecure-requests" always;
root /var/www/html;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# 传递真实 HTTPS 状态,杜绝 Canonical 生成为 http://
fastcgi_param HTTPS on;
fastcgi_param HTTP_X_FORWARDED_PROTO https;
}
# 静态资源长期缓存与无脚本混入
location ~* \.(jpg|jpeg|png|gif|ico|webp|svg|css|js)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
}
改完配置并在服务器执行 nginx -t && nginx -s reload 后,丢掉图形化浏览器,在终端用以下三条命令核对:
# 1. 验证 80 端口是否为干净的 301(重点关注是否多出额外的跳转跳数)
curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" http://www.domain.com/article/1082.html
# 2. 模拟搜索引擎爬虫抓取 443 端口,查看 HSTS 响应头
curl -I -A "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)" https://www.domain.com/
# 3. 抓取页面内容,确认 canonical 链接与站内资源协议
curl -s https://www.domain.com/article/1082.html | grep -i "canonical"
只要 80 端口是单跳 301、Canonical 指向明确的 HTTPS 地址、正文没有被拦截的 HTTP 外链,剩下的就是去百度搜索资源平台和 Google Search Console 提交 HTTPS 网站改版规则。等待爬虫重新建立全站信任,收录和索引自然会稳步恢复。
文章标题:为了安全整站切了 HTTPS 配上 HSTS,收录权重却迟迟不涨?排查 307 内部伪重定向、HTTP 混合内容拦截与 Canonical 协议错配我踩过的几个坑
文章链接:https://www.llbbs.cn/jishujaocheng/257.html
本站文章均为原创,未经授权请勿用于任何商业用途
评论一下?