浏览器 SSL Mixed Content 警告

站点是 HTTPS 但浏览器显示 "Not fully secure"——HTTPS 页加载了 HTTP 资源。

你刚把站点迁到 HTTPS。证书合法,地址栏短暂出现了锁图标——下一秒变成感叹号或三角形,写着 Not fully secure / Your connection to this site is not fully secure。打开 DevTools,能看到这种行:

Mixed Content: The page at 'https://yourdomain.com/' was loaded over HTTPS,
but requested an insecure element 'http://cdn.example.com/banner.jpg'.
This content should also be served over HTTPS.

更严格的情况下,资源会被直接屏蔽——hero 图、支付表单、地图组件压根不出来。这就是 mixed content(混合内容),现代浏览器把它当作安全倒退处理:Chrome 从 86 版起会自动升级被动资源,主动资源(脚本、iframe、fetch、XHR)直接拦掉。本文按”我属于哪种 mixed content”→ 永久修复 的思路来排查。

被动 vs 主动 mixed content(浏览器处理方式不同)

类型例子Chrome 行为
被动<img><audio><video> 走 HTTP自动升级到 HTTPS;升级失败则回落 HTTP;控制台警告
主动<script><link rel="stylesheet"><iframe>fetch()XMLHttpRequestWebSocket直接拦截,元素根本不加载

所以你有时看到”Not secure”但页面没明显问题(被动),有时看到的”Not secure”伴随明显坏掉的功能(主动被拦)。

先判断属于哪一种

情况 1:自家模板里硬编码了 http://

老 WordPress 主题、手写 HTML、多年前复制粘贴的模板,里面 src= / href= / srcset= 是绝对的 http:// URL。

怎么看

# 项目源码里
grep -rn 'src="http://' src/ public/ templates/
grep -rn 'href="http://' src/ public/ templates/
grep -rn '"http://' src/ | grep -v 'http://localhost'

浏览器里 DevTools → Network → 过滤 “http” → 找非 localhost 的 HTTP 请求。

修复http:// 改成 https://。不确定目标支不支持 HTTPS,先 curl -I https://that-host.com/path 试一下。

情况 2:老 CMS / 数据库内容引用了 HTTP

WordPress / Ghost / 自研 CMS 里几千篇旧文章正文里硬编码了 <img src="http://yourdomain.com/wp-content/uploads/...">。模板没问题,数据库内容有问题。

怎么看:DevTools 里 mixed content 警告引用的是你自己的域名走 HTTP,类似 http://yourdomain.com/...,不是第三方。

修复(WordPress):

# 用 wp-cli,不要直接写 SQL UPDATE —— PHP 序列化数据会被破坏
wp search-replace 'http://yourdomain.com' 'https://yourdomain.com' \
  --all-tables --skip-columns=guid --dry-run

# 满意后去掉 --dry-run

其他 CMS:用它自己的迁移 / 替换工具。含序列化数据的表绝对不要直接跑 SQL 替换(会破坏长度前缀)。

情况 3:第三方 widget / 嵌入只有 HTTP 版

老牌统计脚本、社交分享按钮、聊天 widget、广告位 provider 没做 HTTPS 支持。要么没有 HTTPS endpoint,要么 HTTPS endpoint 证书有问题。

怎么看:DevTools 里 mixed content URL 是第三方域名。curl -I https://that-provider.com/widget.js 失败或报 SSL 错。

修复:(a) 联系服务方拿 HTTPS endpoint —— 多数都有,只是你用的代码片段太旧;(b) 把 widget 自托管:用自己的 HTTPS 反代;(c) 直接砍掉这个 widget。

情况 4:协议相对 URL(//cdn...)在开发环境跑到 HTTP

老代码用 //cdn.example.com/lib.js(不带协议),意思是”跟页面协议一致”。HTTPS 页就走 HTTPS,http://localhost 开发环境就走 HTTP——线上不出问题,开发出问题,常被错诊断。

怎么看:警告只在 localhost / 走 HTTP 的 staging 出现,线上没事。

修复:换成显式 https://。协议相对 URL 在 2014 年就被 W3C 弃用了,因为它鼓励 HTTP 回落路径。

情况 5:内联 JS / fetch / WebSocket 加载的资源

HTTPS 页里 fetch('http://api.example.com/data')new WebSocket('ws://...')。主动 mixed content——会被拦。报错往往是 DevTools 控制台里一个 fetch 失败,不带 “mixed content” 字样。

怎么看:DevTools Network 里一个 fetch / XHR 状态写 (blocked:mixed-content)。从 HTTPS 页连 ws:// 的 WebSocket 会报 “An insecure WebSocket connection may not be initiated.”。

修复:JS 里 http://https://ws://wss://。如果 API 不支持 HTTPS,客户端救不了——只能在自己服务器上 HTTPS 反代过去。

情况 6:<iframe> 来源是 HTTP

iframe 是主动内容,HTTPS 页里 <iframe src="http://..."> 直接被拦。

怎么看:iframe 区域空白,或浏览器自己显示错误。DevTools 控制台:Mixed Content ... iframe ... was blocked.

修复:换 HTTPS 源。一些老嵌入服务(旧地图、archive.org 2017 前的接口)只有 HTTP 嵌入——找新 endpoint 或自己托管一份。

最短修复路径

按命中率排序:

  1. DevTools → Console → 过滤 “Mixed Content” —— 拿到准确的违规 URL 清单。
  2. 分类:自家域名走 HTTP(CMS / 数据库)、自家模板(源码 find-replace)、第三方(按 provider 调研)。
  3. 自家域名 HTTP:跑一次数据库 search-replace。
  4. 模板硬编码:grep 源码、替换、提交、重新部署。
  5. 第三方:先查有没有 HTTPS endpoint;没有就砍或者反代。
  6. 作为兜底,在 HTTP 响应头加 Content-Security-Policy: upgrade-insecure-requests —— 浏览器会自动把所有子资源的 http:// 重写成 https://。被动资源静悄悄就好了;主动资源仍要求目标真的支持 HTTPS 才能跑起来。
# nginx
add_header Content-Security-Policy "upgrade-insecure-requests" always;
<!-- 设不了响应头时退而求其次 -->
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
  1. 验证:新开无痕窗口硬刷,DevTools 控制台应该是空的,地址栏锁图标是实心绿。

怎么确认修好了

# 扫一遍自己站点找 HTTP 引用
# 方式 A:用 Mozilla Observatory
curl -s "https://observatory.mozilla.org/api/v1/analyze?host=yourdomain.com" | jq

# 方式 B:命令行扫
curl -s https://yourdomain.com/ | grep -oE 'http://[^"]+' | sort -u

# 方式 C:专门的扫描器(推荐)
# https://www.whynopadlock.com/
# https://www.jitbit.com/sslcheck/
# 两者都会爬多页并按 URL 报告 mixed content

页面多的站点,唯一靠谱的长期保险是 CI 任务:grep -r 'http://' dist/ 命中就 fail build。

预防

  • 所有新内容都用绝对 https:// URL。 协议相对 // 已弃用,会让开发和生产环境表现不一致。
  • 全局响应头里加 Content-Security-Policy: upgrade-insecure-requests —— 一行就能抵挡被动 mixed content 重新混进来。
  • 加 CI 检查,扫构建后的 HTML 里非注释中的 http://,命中就 fail。
  • WordPress / Ghost / CMS 站点:把规范站点 URL 一次性设到 https://,每次主题 / 插件更新后都验证一遍(有的主题会发硬编码 HTTP)。
  • 每年审查一次第三方嵌入 —— 服务方偶尔会下线 HTTPS 或换 endpoint。过期的嵌入代码是最常见的回归来源。

FAQ

问:mixed content 会影响 Google 排名吗? 答:不直接,但间接会。HTTPS 自 2014 年起就是排名信号。一个明明走 HTTPS 却显示”Not secure”的页面会失去用户信任,跳出率和停留时间下降,反过来影响排名。Google 还会在 Chrome 地址栏屏蔽 mixed content 图片,影响转化。

问:upgrade-insecure-requests 能修所有问题吗? 答:能静默修复被动资源(图、音、视频)。也会尝试升级主动资源(脚本、iframe),但目标服务器不真支持 HTTPS 时,升级请求会失败,元素仍然加载不出来。所以每个目标都得支持 HTTPS——upgrade-insecure-requests 只是省了你手动改每条 URL。

问:我已经改了 WordPress 的站点 URL,怎么还提示 mixed content? 答:站点 URL 只影响新内容。老文章正文里硬编码着 http://。跑 wp search-replace 'http://yourdomain.com' 'https://yourdomain.com' --skip-columns=guid 修数据库。--skip-columns=guid 不能省,省了会把老 feed 阅读器搞坏。

问:只有一个页面报 mixed content 警告,怎么回事? 答:那个页面引用了别的页面没有的东西——通常是某张横幅图、某个自定义嵌入或一段一次性的第三方脚本。打开那个具体页面的 DevTools,Network 看 HTTP 请求。

问:我用的第三方 widget 只有 HTTP 版,对方拒绝加 HTTPS。怎么办? 答:自己服务器上反代。在 https://yourdomain.com/widget-proxy/ 加一条路由,服务端拉那个 HTTP 资源,用你的 HTTPS 发出去。多一点延迟和带宽,但短期内除了砍掉 widget 这是唯一办法。

相关阅读

标签: #域名 #DNS #SSL #排查 #SSL 混合内容