证书装上,不等于 HTTPS 生效
WordPress 装上 SSL 之后报「不安全」,多数人第一反应是证书没装好。实际上证书只是五件事里的一件,另外四件都不在后台报错:页面里还挂着 http 的图片、跳转链跳了两次、数据库里存着一堆 http://、GSC 里还留着旧地址。
其中出现频率偏高的一类叫混合内容(mixed content)——页面本身是 https,但里面引用了 http 的资源,浏览器就把整页判成不安全。这一项单独占第三节,因为它是装完证书还报错的头号原因。
| 你看到的现象 | 大概率是 | 看第几节 |
|---|---|---|
| 锁头带感叹号、图片加载不出来 | 混合内容 | 三 |
| 电脑正常、安卓手机与爬虫报错 | 证书链不完整 | 一 |
| 地址栏里的 https 会自动变回 http | 跳转规则配反了 | 二 |
| 站内链接点进去跳回 http | 数据库里存着绝对地址 | 四 |
| 搜索结果里出现 http 版本 | 外部记录没同步 | 五 |
拿着症状直接查表,比从第一条开始试快得多。全部过一遍,通常半小时。
顺便说清一件事:HTTPS 只保护「传输」这一段。后台怎么防爆破、权限怎么收紧,是另一层,见 WordPress 登录安全。
一、证书本身:三个字段对一遍
浏览器地址栏有锁,只能说明「证书被信任」,不能说明「装对了」。要确认三件事:
| 查什么 | 常见问题 | 怎么查 |
|---|---|---|
| 域名匹配 | 只签了 example.com,但站点跑在 www.example.com 上 | 访问两个版本,看是否都无警告 |
| 证书链完整 | 少了中间证书,安卓与部分爬虫直接握手失败 | SSL Labs 评级,A 以下要修 |
| 到期时间 | Let’s Encrypt 只有 90 天,自动续签没配就等着过期 | 命令行看 notAfter |
命令行一次看全:
openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
如果 subject 里的 CN 或 SAN 不含你实际访问的域名,锁头绿也只是碰巧——换一个子域名就报错。
二、跳转:只跳一次
常见的配置错误是「跳两次」:http://example.com 先 301 到 https://example.com,再 301 到 https://www.example.com。两次握手,移动端能明显感觉到慢。
Nginx 里应该让它一次跳到位:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://www.example.com$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/fullchain.pem;
ssl_certificate_key /path/privkey.pem;
return 301 https://www.example.com$request_uri;
}
宝塔面板里对应的是「网站 → 设置 → SSL → 强制 HTTPS」,但它只处理 http→https,www 与非 www 的归并要自己写规则,否则还是两次跳转。
验证方法:curl -sIL http://example.com 看返回几个 301。超过一个就说明有跳转链。
三、混合内容:页面里还挂着 http
这是 HTTPS 之后报「不安全」的头号原因。页面本身是 https,但里面有一张 http:// 的图片、一段 http:// 的脚本,浏览器就会把整页降级提示。
| 类型 | 表现 | 危害 |
|---|---|---|
| 被动混合内容 | 图片、音视频走 http | 浏览器降级提示,锁头变灰或加感叹号 |
| 主动混合内容 | JS、CSS、字体走 http | 直接拦截,页面排版与交互坏掉 |
定位办法:开浏览器控制台,筛选 Mixed Content;或用 curl -s https://你的站 | grep -o 'http://[^"]*' 粗筛一遍。
修的顺序:先改模板里写死的地址,再改数据库里的,再考虑用 CSP 兜底。
四、数据库里的硬编码:替换不能直接 UPDATE
老文章里写的「到设置→常规把地址改成 https」只解决了两个字段。真正残留的是文章正文、自定义字段、小工具、主题选项里存着的绝对地址。
千万不要直接跑 UPDATE ... SET post_content = REPLACE(...)。 WordPress 的很多字段是 PHP 序列化字符串,格式是 s:长度:"内容"——长度写死了。你把 http 改成 https,字符串变长了,长度前缀没变,反序列化直接失败,页面白屏或字段丢失。
用 WP-CLI:
wp search-replace 'http://www.example.com' 'https://www.example.com' --all-tables --precise
wp cache flush
--precise 会正确处理序列化长度。跑之前先备份数据库——这一步出问题,回滚成本比建站高。
替换完必须清缓存,否则你看到的是缓存里的旧页面,会误判成没生效。缓存怎么清干净,见 清除缓存没生效怎么查。
五、GSC、canonical 与 sitemap
前面四步做完,站内已经是 https 了。但外部还有三个地方记着旧地址:
| 位置 | 要做什么 |
|---|---|
| Search Console | https 版本重新验证一次;旧 http 资源保留 301,不要删,让谷歌自己并过去 |
| canonical | 检查是否还输出 http;主题或 SEO 字段里写死过就会漏 |
| sitemap | 重新生成,确认里头的 URL 全是 https |
另外,如果你用了 CDN,回源地址也要一起改。CDN 回源走 http 的情况很常见——访客看到的是 https,源站到 CDN 这一段是明文,某些场景下浏览器仍会报混合内容。CDN 这一层的判断见 WordPress CDN 指南。
六、HSTS 与续签:两个容易忘的收尾
HSTS 告诉浏览器「以后只用 https 访问我」。加上这一行:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
但它有个坑:一旦下发,在 max-age 到期前浏览器不会让你回退到 http。证书出问题、配置回滚,访客会直接打不开站。所以顺序是:先确认 https 稳定跑满一个月,再开 HSTS;第一次用短一点的 max-age(比如 300 秒)试水,再逐步拉长。
续签这一块,Let’s Encrypt 是 90 天。宝塔面板默认每天凌晨尝试续签,但如果 80 端口被防火墙挡了、域名解析改过、或者站点目录权限变了,验证会静默失败——你不会收到任何提示,直到过期那天全站报错。
建议加一条到期前的检查:到期前 30 天没续上就告警。这一项在我们的托管服务里是标准动作,见 WordPress 网站维护与托管。
七、五个检查点对照表
| # | 检查 | 不合格的表现 | 修法 |
|---|---|---|---|
| 1 | 证书域名与链 | 部分设备/爬虫握手失败 | 重新签发含 www 与非 www 的证书 |
| 2 | 跳转次数 | 首屏慢、移动端尤其明显 | 合并成一次 301 |
| 3 | 混合内容 | 锁头灰、感叹号、排版乱 | 改模板与库里写死的 http |
| 4 | 数据库残留 | 内链跳 http、图片加载失败 | WP-CLI search-replace,先备份 |
| 5 | 外部记录 | 收录分散、canonical 指错 | GSC 重新验证、sitemap 重生成 |
八、新站不用折腾这件事
如果你正在建站,别走「先 http 再迁 https」这条路。开工就上 https,省掉上面五步里的大半——数据库里从第一天就没有 http:// 的绝对地址,跳转规则一次写对,canonical 从模板层就输出 https。
迁移真正贵的不是技术动作,是漏项排查。少一次迁移,就少一轮漏项。
HTTPS 只是安全的一层。账号强度、更新是否及时、服务器配置有没有疏漏,这几件漏一件,HTTPS 都挡不住入侵——完整顺序见 WordPress 安全加固指南。