侧边栏壁纸
  • 累计撰写 197 篇文章
  • 累计收到 2 条评论

为了安全整站切了 HTTPS 配上 HSTS,收录权重却迟迟不涨?排查 307 内部伪重定向、HTTP 混合内容拦截与 Canonical 协议错配我踩过的几个坑

2026-10-15 / 0 评论 / 5 阅读
AI 摘要由 AI 生成

文章指出作者在将网站迁移至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。

这在搜索引擎眼里形成了一个逻辑死循环:

  1. 爬虫访问 https://www.domain.com/article/1082.html(状态码 200)。
  2. 爬虫解析 HTML,看到 Canonical 标签明确声明:“本页面的规范母版本是 http://www.domain.com/article/1082.html”。
  3. 爬虫遵从指令,跑去抓取 HTTP 版本。
  4. 80 端口返回 301,强跳回 HTTPS 版本。
  5. 爬虫再次拿到 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 网站改版规则。等待爬虫重新建立全站信任,收录和索引自然会稳步恢复。

评论一下?

OωO
取消