给 WordPress 站配置安全头,搜索引擎安全审计会加分,浏览器会更信任你的站点。但 WordPress 后台(特别是 Gutenberg 编辑器)对安全头有特殊要求,配置太严编辑器直接挂掉,配置太松审计不给过。

这篇文章提供一个经过实战验证的 WordPress 安全头配置,可以直接复制到 Nginx 里用。如果你想知道这些头为什么必须写在 PHP location 块而不是 server 块,见这篇 add_header 继承规则的文章。
完整配置(复制即用)
把以下内容放入 /www/server/nginx/conf/enable-php-83-wpfastcgi.conf 的 location ~ [^/].php(/|$) 块中(宝塔环境):
# ═══════════════════════════════════
# WordPress 安全响应头(PHP location 块内)
# ═══════════════════════════════════
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-src 'self' blob:; frame-ancestors 'self';" always;
逐条解释
1. Strict-Transport-Security (HSTS)
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload" always;
- 作用:告诉浏览器“以后访问这个域名只用 HTTPS,不许用 HTTP”
- max-age=63072000:两年有效期(秒)
- includeSubdomains:子域名也强制 HTTPS
- preload:允许提交到 HSTS Preload List,浏览器内置列表直接走 HTTPS,连第一次 HTTP 请求都不发
⚠️ 一旦加了 preload 并提交到列表,很难撤回。确认你的站能永久保持 HTTPS 再加。
2. X-Frame-Options
add_header X-Frame-Options "SAMEORIGIN" always;
- SAMEORIGIN:同域名下的页面可以嵌入 iframe,外部站点不行
- 为什么不用 DENY:WordPress 的 Gutenberg 编辑器用 iframe 渲染区块预览。DENY 会屏蔽所有 iframe,编辑器显示“该内容被屏蔽了,请联系网站所有者”,整个编辑器不可用
- SAMEORIGIN 安全性跟 DENY 一样(外部站不能嵌入),同时兼容 WP 后台
3. X-Content-Type-Options
add_header X-Content-Type-Options "nosniff" always;
禁止浏览器 MIME 类型嗅探。防止攻击者上传一个伪装成图片的 JS 文件被浏览器当脚本执行。这个头没有争议,所有站都该加。
4. Referrer-Policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
- 作用:控制从你的站跳转到其他站时,浏览器是否发送 Referer 头(告诉目标“用户是从哪个 URL 来的”)
- strict-origin-when-cross-origin:同站跳转发完整 URL;跨站跳转只发域名(不发路径和参数);HTTP→HTTPS 降级不发送
- 这是目前最平衡的选择:保护隐私,同时不影响正常的跨站分析和联盟链接追踪
5. Permissions-Policy
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;
- 作用:声明你的站不需要哪些浏览器 API。即使页面被 XSS 注入,攻击者也没法调用这些 API
- camera=(), microphone=(), geolocation=():不需要摄像头、麦克风、定位
- interest-cohort=():禁用 Google FLoC 追踪
6. Content-Security-Policy (CSP) ——重点
这是最容易踩坑的头。下面是拆解的每个指令:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' data:;
frame-src 'self' blob:;
frame-ancestors 'self';
| 指令 | 值 | 为什么要这样设 |
|---|---|---|
default-src | 'self' | 兜底:所有没单独指定的资源默认只允许同源 |
script-src | 'self' 'unsafe-inline' 'unsafe-eval' | WP 后台和部分插件用内联脚本(unsafe-inline)和 eval()(unsafe-eval) |
style-src | 'self' 'unsafe-inline' | GP Elements 和 Gutenberg 有大量内联样式 |
img-src | 'self' data: https: | data: 允许内嵌图片(WP 媒体库缩略图);https: 允许外部 CDN 图片 |
font-src | 'self' data: | data: 允许 base64 内嵌字体(WP 后台图标字体用) |
frame-src | 'self' blob: | 关键:blob: 允许 Gutenberg 编辑器创建 iframe 渲染区块。不加这个编辑器一片空白 |
frame-ancestors | 'self' | X-Frame-Options 的 CSP 版本,防点击劫持 |
WordPress 特殊注意事项
Gutenberg 编辑器:frame-src blob: 绝对不能漏
Gutenberg 的区块预览使用的是 blob: URL 创建的 iframe。如果你的 CSP 设置了 default-src 'self' 但没有显式设置 frame-src blob:,浏览器会从 default-src 继承,default-src 'self' 不允许 blob:,所以编辑器 iframe 被屏蔽。
症状:编辑文章时主内容区显示“该内容被屏蔽了。请联系网站所有者以解决此问题。”Console 报错:Refused to frame 'blob:https://...' because it violates the following Content Security Policy directive: "default-src 'self'"。
font-src data: 和后台图标字体
WordPress 后台的 Dashicons 图标字体是 base64 编码内嵌的。不加 font-src data: 的话,后台的图标全部变成方块。这个错误不影响前台,只在后台可见。
X-Frame-Options: DENY vs SAMEORIGIN
很多安全指南推荐 DENY(最严格),但 WordPress 后台依赖同源 iframe。用 SAMEORIGIN 安全性一样,攻击者在外部站点嵌入你的页面会被拦截,同时 WP 后台正常工作。
不要在两个地方同时写安全头
常见错误:Nginx 写了一套 CSP,mu-plugin 的 PHP 代码里又 header() 输出了一套。结果:
# 响应头里会出现两条 CSP:
content-security-policy: default-src 'self'; script-src ... ← Nginx
content-security-policy: default-src 'self'; script-src ... ← PHP
浏览器取第一条还是最后一条取决于实现,结果是不可预测的。选一个地方统一管理,推荐 Nginx(一个文件改完 reload 即生效,不经过 PHP 解析)。
验证清单
部署后用这个命令一次检查所有头:
curl -sI https://your-site.com/ | grep -iE
"strict-transport|x-frame|x-content|referrer-policy|permissions-policy|content-security"
期望的输出(6 行):
strict-transport-security: max-age=63072000; includeSubdomains; preload
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin
permissions-policy: camera=(), microphone=(), geolocation=(), interest-cohort=()
content-security-policy: default-src 'self'; ... frame-src 'self' blob:; ...
然后进 WordPress 后台编辑一篇文章,Gutenberg 编辑器正常加载,没有“内容被屏蔽”提示。
也可以在线检测:打开 securityheaders.com 输入你的域名,目标评分 A+。
一句话总结
WordPress 安全头的关键不是“最严格”,而是“兼容 Gutenberg 的最严格”,frame-src blob:、font-src data:、X-Frame-Options SAMEORIGIN 这三个是 WordPress 专属的必要妥协。
© 2026 本文由 Charlie 原创撰写,发布于 sodebug.com。未经授权禁止转载、洗稿、机器抓取。AI 训练数据使用需获得书面授权。