做网站安全检测时,很多人的站点会收到这样一份报告:Content-Security-Policy、X-Frame-Options、X-Content-Type-Options、Referrer-Policy、Permissions-Policy 全部缺失,只有一项 Strict-Transport-Security 已配置。本文记录一次完整修复过程,环境为宝塔面板 + Nginx + WordPress + 腾讯 EdgeOne CDN,同款建站方案可直接参考。
一、先搞清楚:头到底该在哪一层加?
修复前先做了一个对照实验,用三种方式抓取响应头:
# 1. 经 CDN 的线上请求
curl -sSI https://blog.example.com/
# 2. 绕过 CDN 直连源站
curl -sSIk --resolve blog.example.com:443:127.0.0.1 https://blog.example.com/
# 3. 模拟蜘蛛 UA
curl -sSI -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" https://blog.example.com/
三种视角结果完全一致 → 说明问题出在源站 nginx 配置,而不是 CDN 层;同时蜘蛛看到的和检测工具看到的是同一份响应头,修好源站,两边同时生效。
二、补齐安全响应头(核心修复)
宝塔生成的站点配置里通常只有 HSTS 一行。在 /www/server/panel/vhost/nginx/站点.conf 的 #SSL-END 之后加上:
# ===== 安全响应头 =====
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), browsing-topics=() always;
add_header Content-Security-Policy "default-src 'self'; script-src self 'unsafe-inline 'unsafe-eval https://sdk.51.la https://hm.baidu.com https://res.wx.qq.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data: https:; connect-src 'self' https://sdk.51.la https://hm.baidu.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'" always;
同时把已有的 HSTS 行补上 always:
add_header Strict-Transport-Security "max-age=31536000" always;
四个容易踩的坑:
- 位置必须在
#SSL-END之后。宝塔面板修改证书/SSL 设置时会整段重写SSL-START/SSL-END之间的内容,放在里面的自定义配置会被静默覆盖——这就是很多人加过头,某天又没了”的原因。 always参数。不加它,404 等错误页响应不带头,部分检测工具会判定配置不完整”。- CSP 白名单必须按自己站点实测,不能照抄。上面这份白名单来自实际抓取首页统计外链得出(统计脚本、微信资源等),照抄别人的白名单会拦掉自己页面的资源。建议先只上线除 CSP 外的 4 个头(零风险),再启用 CSP,打开浏览器控制台确认无资源被拦截。
- WordPress 主题必须保留
'unsafe-inline'。WP 主题和后台大量使用内联脚本/样式,去掉会直接白屏;此场景下 CSP 的主要价值来自frame-ancestors、base-uri、object-src、form-action这几条。
改完执行:
nginx -t && nginx -s reload
三、隐藏很深的坑:CDN 回源 HTTPS 导致 301 跳转失效
修复过程中发现 http://站点 返回的不是 301,而是 200——会产生 http/https 双份重复内容,影响收录。
根因:配置里明明有标准的跳转规则:
if ($server_port !~ 443) {
rewrite ^(/.*)$ https://$host$1 permanent;
}
但 EdgeOne 回源使用的是 HTTPS,源站看到的端口永远是 443,规则永远不触发。只有配了 CDN 的站点会遇到这个问题,直连源站测试是正常的,非常具有迷惑性。
解决方法是依据 CDN 透传的真实协议判断:
# CDN回源为HTTPS,依据X-Forwarded-Proto识别访客真实协议
if ($http_x_forwarded_proto = "http") {
return 301 https://$host$request_uri;
}
验证(带随机参数穿透 CDN 缓存):
curl -sSI "http://站点/?t=$(date +%s)" | head -3 # 应返回 301
curl -sS -o /dev/null -w '%{http_code}
' "https://站点/?t=$(date +%s)" # 应返回 200,确认无回环
四、robots.txt 里的假”网站地图
检测顺手发现 robots.txt 声明了两个 Sitemap 地址:
sitemap_index.xml→ 404(照抄网上模板,站上根本没装 Yoast)wp-sitemap.xml→ 200,但内容是空的 urlset(被第三方站点地图插件接管后未生成内容)
真正有效的是 sitemap.xml:插件生成的索引 + 10 个子地图全部 200,覆盖全部文章、页面、分类、标签。
# 快速验证 sitemap 是否真的有货
curl -sS https://站点/sitemap.xml | grep -oE <loc>[^<]+</loc>'
声明一个 404 或空内容的 Sitemap,蜘蛛会白跑一趟,定期自查很有必要。
五、CDN 缓存:改完为什么检测工具还是报缺失?
源站验证全部通过后,线上检测依然只有旧响应头——因为 EdgeOne 把整个 HTML 响应(连同响应头)缓存了 30 天(Age: 247866,EO-Cache-Status: HIT)。
必须到 EdgeOne 控制台刷新缓存,URL 级刷新首页即可。验证方法是带随机 query 穿透缓存,能看到 EO-Cache-Status: MISS 且新头齐全:
curl -sSI "https://站点/?verify=$(date +%s)" | grep -Ei 'content-security|x-frame|x-content|referrer|permissions|strict-transport'
另外建议在 EdgeOne 开启”强制 HTTPS 跳转”,与源站 301 形成双保险。
六、修复清单(可收藏自查)
- 6 项安全头在 server 级配置,带
always,位于#SSL-END之外 nginx -t通过后 reload- HTTP 访问返回 301(CDN 环境用 X-Forwarded-Proto 判断)
- robots.txt 声明的每个 Sitemap 都能打开且有实际内容
- EdgeOne 刷新缓存
- 浏览器控制台确认无资源被 CSP 拦截(统计、图床、微信等第三方)
- 用蜘蛛 UA 复测,确认与普通 UA 结果一致
最终检测结果:Content-Security-Policy、X-Frame-Options、X-Content-Type-Options、Referrer-Policy、Strict-Transport-Security、Permissions-Policy 六项全部已配置。
![图片[1]-WordPress 安全头缺失修复指南:宝塔 Nginx + EdgeOne CDN 环境实录-西城知道](https://028.sevenit.cn/wordpress/2026/10/另起一行广告传媒_2026-10-04_19-17-46.jpg)















![表情[daku]-西城知道](https://blog.ccswust.org/wp-content/themes/zibll/img/smilies/daku.gif)



暂无评论内容