保哥这些年在做 SEO 与运维交叉的活儿,最常被朋友问到的一类问题就是:网站买了证书装好了 HTTPS,怎么浏览器有时候输 www 进得去、有时候输 http 也能打开,搜索引擎收录的 URL 五花八门,搞得权重特别分散。这其实不是 SSL 没装好,而是 301 跳转链路没设计完整。一个站点的标准 URL 只能有一个,《百度搜索引擎网页质量白皮书》里写得很清楚:每一个页面都应该只对应一条唯一的 URL,否则搜索引擎会把同一页内容当成多份重复来收录,权重被稀释、排名上不去,甚至触发重复内容降权。本文保哥把自己 zhangwenbao.com 这台机器上跑了好几年的实测配置完整拆开来讲清楚,按 Nginx 1.10.1 起步、向上兼容到 1.24 LTS 都能照抄,让所有非主域名形态的访问全部 301 永久跳转到 https://zhangwenbao.com 这一条目标 URL 上。
为什么必须把所有变体都 301 到唯一主域名
保哥先把 SEO 这一头的逻辑捋直,因为很多人只觉得「能打开就行」,根本没意识到背后的代价。一个 WordPress 或 Typecho 这样的内容站点,如果同时存在 http://example.com、http://www.example.com、https://www.example.com 和 https://example.com 这四种形态,搜索引擎抓取时会把它们识别成四个独立站点,外链权重被切成四份,关键词排名互相内耗。再加上社交媒体上别人随手分享的链接、旧文章里残留的硬编码 URL、第三方导航站给的反链,这些指向不一形态的流量最终全都浪费掉了。
301 永久重定向是搜索引擎认可的「URL 合并」信号,Google、百度、必应都明确把 301 当成权重传递的合法途径。当用户或爬虫访问 http://www.example.com 时,服务器返回 301 状态码并指向 https://example.com,搜索引擎会把前者的所有累计权重平滑迁移到后者,前者从索引中逐步消失,后者越来越权威。换成 302 临时跳转就完全不是这个效果了,权重传递会非常滞后甚至不传,所以保哥的标准做法永远是 301。
这里要先决定一个方向性问题:到底是 www 跳到非 www,还是非 www 跳到 www。技术上两种都行,但保哥个人统一选不带 www 的形式作为主站,原因有三。第一,URL 更短,分享时少四个字符;第二,国内大部分手机浏览器地址栏默认补全的就是不带 www 的形式;第三,未来要做子域名拆分(比如 blog.example.com、shop.example.com)时不会和 www 这个特殊子域名冲突。一旦选定方向就不要再改,因为反复改跳转规则会让搜索引擎重新走一轮权重合并周期。
Nginx 三段式配置的整体设计
保哥用的 zhangwenbao.com 这台机上是 Nginx + PHP-FPM 跑 Typecho 的经典栈,整个 301 跳转方案一共拆成三个 server 块,分别监听三种入口流量并把它们汇集到唯一主域名:
第一个 server 块监听 80 端口,处理所有 HTTP 流量,把 http://zhangwenbao.com 和 http://www.zhangwenbao.com 一并 301 到 https://zhangwenbao.com。第二个 server 块监听 443 端口,绑定主域名 zhangwenbao.com,这是真正承载内容的服务端。第三个 server 块同样监听 443 端口,但只绑定 www.zhangwenbao.com,作用是接住「带 www 的 HTTPS 流量」并 301 到不带 www 的主域名。
这种三段式拆法的好处是职责单一、互不干扰:80 端口的所有请求一律重定向到 HTTPS,主站 server 只关注内容输出,www 子域名 server 只关注跳转。出问题时排查也快,看 Nginx 错误日志的 server_name 字段就能定位是哪一段在报错。下面三节分别给完整代码。
第一段配置:把所有 HTTP 流量统一升级到 HTTPS
这一段对应 80 端口的两个域名形态。保哥的做法是用 return 301 而不是 rewrite,前者是 Nginx 提供的快路径,性能比 rewrite 好一些,而且写法更直观。完整代码如下:
server {
listen 80;
server_name zhangwenbao.com www.zhangwenbao.com;
root /web/www/zhangwenbao_com;
return 301 https://zhangwenbao.com$request_uri;
if (-f $request_filename/index.html) {
rewrite (.*) $1/index.html break;
}
if (-f $request_filename/index.php) {
rewrite (.*) $1/index.php;
}
if (!-f $request_filename) {
rewrite (.*) /index.php;
}
# {{{ block/deny
## Block download agents
if ($http_user_agent ~* LWP::Simple|BBBike|wget) {
return 403;
}
## Block some robots
if ($http_user_agent ~* msnbot|scrapbot) {
return 403;
}
## Deny certain Referers
if ($http_referer ~* (babes|forsale|girl|jewelry|love|nudit|organic|poker|porn|sex|teen)) {
return 403;
}
# }}}
location / {
index index.php index.html index.htm;
}
location = /50x.html {
root html;
}
location ~* apple-touch-icon {
access_log off;
rewrite .* /fav-icon.png last;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
access_log /web/log/nginx/default.access.log dynamic;
}关键就是 return 301 https://zhangwenbao.com$request_uri; 这一行。$request_uri 是 Nginx 内置变量,包含原始请求的完整路径和查询参数,这样跳转之后 URL 的 path 和 query 都会被原样保留下来。比如用户访问 http://www.zhangwenbao.com/archives/123.html?ref=weibo 会被精确跳到 https://zhangwenbao.com/archives/123.html?ref=weibo,不会丢任何信息,对统计追踪和深度链接都很友好。
保哥这里多说一句关于 return 和 rewrite 的选择。早年配置文件里大家更习惯写 rewrite ^/(.*)$ https://example.com/$1 permanent;,效果上和 return 301 https://example.com$request_uri; 类似,但 rewrite 需要 Nginx 走一遍正则匹配,性能稍差。Nginx 官方文档现在更推荐 return 方案,特别是这种简单的整站跳转场景。
下面那块 if + rewrite 三连其实是为了兼容某些静态文件路径而保留的,新装机的话可以用 try_files $uri $uri/ /index.php?$args; 替代,写法更现代也更稳。Nginx 官方一直警告 location 块里的 if 是 evil 的,能不用就不用。后面会给现代化版本。
第二段配置:主域名 443 端口承载真实内容
这一段是真正的网站服务端,所有内容请求最终都会落到这里:
server {
listen 443 ssl http2;
server_name zhangwenbao.com;
root /web/www/zhangwenbao_com;
index index.html index.htm index.php;
ssl_certificate cert/zhangwenbao_com.pem;
ssl_certificate_key cert/zhangwenbao_com.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_prefer_server_ciphers on;
if (-f $request_filename/index.html) {
rewrite (.*) $1/index.html break;
}
if (-f $request_filename/index.php) {
rewrite (.*) $1/index.php;
}
if (!-f $request_filename) {
rewrite (.*) /index.php;
}
# {{{ block/deny
if ($http_user_agent ~* LWP::Simple|BBBike|wget) {
return 403;
}
if ($http_user_agent ~* msnbot|scrapbot) {
return 403;
}
if ($http_referer ~* (babes|forsale|girl|jewelry|love|nudit|organic|poker|porn|sex|teen)) {
return 403;
}
# }}}
location / {
index index.php index.html index.htm;
}
location = /50x.html {
root html;
}
location ~* apple-touch-icon {
access_log off;
rewrite .* /fav-icon.png last;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
access_log /web/log/nginx/default.access.log dynamic;
}注意保哥这里把原来的 ssl on; 写法替换成了 listen 443 ssl http2;,这是 Nginx 1.15 之后官方推荐的现代写法,ssl on; 在 1.25 之后已经被标记为过时。同时顺手开了 HTTP/2,多路复用对静态资源加载速度提升非常明显,对 SEO 的 Core Web Vitals 指标也有正面贡献。
协议方面保哥只保留了 TLSv1.2 和 TLSv1.3,把早年那行配置里的 TLSv1 和 TLSv1.1 都干掉了。这两个老版本协议从 2020 年起就被各大浏览器陆续禁用,并且对 SEO 来说也是减分项——Google PageSpeed 会直接标红老协议。如果你需要兼容某些老设备访问,可以保留 TLSv1.2,但不要再开 TLSv1.0/1.1。
第三段配置:www 子域名 443 端口的纯跳转
这一段是整个三段式中最容易被忽略的一环。很多人只配了前两段,结果用户敲 https://www.zhangwenbao.com 进来,浏览器要么报「证书不匹配」要么直接 502,因为根本没有 server 在 443 端口监听这个 server_name。
server {
listen 443 ssl http2;
server_name www.zhangwenbao.com;
ssl_certificate cert/zhangwenbao_com.pem;
ssl_certificate_key cert/zhangwenbao_com.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_prefer_server_ciphers on;
return 301 https://zhangwenbao.com$request_uri;
}保哥把这一段简化到了极致:只保留监听端口、SSL 证书、协议设置和那条 301 跳转语句,其它什么 location、PHP 处理、错误页面全都不需要,因为这个 server 块的唯一职责就是「接到请求立刻 301 走人」,根本不会走到具体内容输出。
这里 SSL 证书必须挂上,原因是 HTTPS 握手发生在 Nginx 拿到请求 URL 之前。即使你最终要 301 跳转,浏览器也得先和这个 server 完成 TLS 握手才能拿到响应,证书不挂浏览器直接报错。所以保哥用的是同一份能覆盖 zhangwenbao.com 和 www.zhangwenbao.com 的多域名证书(SAN 证书)或者通配符证书,Let's Encrypt 免费证书申请时把两个域名都加进去就行。
申请 Let's Encrypt 证书的命令保哥贴一下,方便新手照抄:
certbot certonly --nginx \
-d zhangwenbao.com \
-d www.zhangwenbao.com \
--email seopatpat@gmail.com \
--agree-tos --no-eff-email证书路径默认在 /etc/letsencrypt/live/zhangwenbao.com/ 下,把 ssl_certificate 和 ssl_certificate_key 路径改过去就行。Let's Encrypt 证书 90 天到期,记得用 certbot renew --quiet 配合 cron 自动续期。
配置上线后的全链路验证清单
保哥每次改完跳转规则都会用一份固定的 curl 清单逐项验证,确保四种入口形态都能正确合并到主域名。这里把命令贴出来,你可以直接 copy 到自己服务器上跑:
# 1. http 不带 www
curl -I http://zhangwenbao.com/
# 2. http 带 www
curl -I http://www.zhangwenbao.com/
# 3. https 带 www
curl -I https://www.zhangwenbao.com/
# 4. https 不带 www(这个应该返回 200)
curl -I https://zhangwenbao.com/前三条都应该看到 HTTP/1.1 301 Moved Permanently 和 Location: https://zhangwenbao.com/ 字样,第四条应该是 HTTP/2 200。如果发现某一条不是 301 而是 302,赶紧回去检查配置,把 return 302 或 redirect 关键词替换成 return 301 或 permanent。302 对 SEO 是减分项,因为搜索引擎会按临时跳转处理,权重不会迁移过去。
保哥再额外提几个高级验证点:
第一,带路径和参数的深链接也要验证一下,比如 curl -I http://www.zhangwenbao.com/archives/123.html?utm_source=weibo,确认 Location 头里 path 和 query string 都被原样保留下来了,没有任何丢失。
第二,访问大小写混用的 URL,确认跳转目标的大小写也保持一致。Nginx 的 server_name 默认大小写不敏感,但 path 部分是敏感的,这点要注意。
第三,登录 Google Search Console 和百度搜索资源平台,提交新的 sitemap.xml,主动告诉搜索引擎主域名是 https://zhangwenbao.com。Google Search Console 里有个「域名属性」选项,添加之后整个域名下所有形态的 URL 都会被统一管理,比单独添加 URL 属性方便得多。
第四,浏览器开 Network 面板访问每一个变体,看 chain 是否一次到位。一个高质量的跳转应该是「http://www → https://」一步 301,而不是「http://www → http:// → https://www → https://」绕好几圈。多跳一次 TTFB 增加几十到几百毫秒,对 SEO 和用户体验都是减分项,多重跳转链一定要打掉。
第五,用 SSL Labs 的在线工具 ssllabs.com/ssltest 跑一遍,确认 SSL 配置评分能拿到 A 或 A+。这个工具会同时检查证书链、协议版本、加密套件强度、HSTS 等十几个维度,是衡量 SSL 配置健康度的事实标准。
一个面向 SEO 的现代化精简版本
如果你是新装机,保哥强烈建议把上面那种 if + rewrite 三连的老式写法直接抛弃,改用 try_files。下面这份是我现在所有新站统一在用的精简模板:
# 80 端口:所有 HTTP 流量升级到 HTTPS 主域名
server {
listen 80;
server_name zhangwenbao.com www.zhangwenbao.com;
return 301 https://zhangwenbao.com$request_uri;
}
# 443 端口 www 子域名:HTTPS 跳到 HTTPS 主域名
server {
listen 443 ssl http2;
server_name www.zhangwenbao.com;
ssl_certificate /etc/letsencrypt/live/zhangwenbao.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/zhangwenbao.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
return 301 https://zhangwenbao.com$request_uri;
}
# 443 端口主域名:真正的内容服务
server {
listen 443 ssl http2;
server_name zhangwenbao.com;
root /web/www/zhangwenbao_com;
index index.php index.html;
ssl_certificate /etc/letsencrypt/live/zhangwenbao.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/zhangwenbao.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
# 强制浏览器记住 HTTPS 一年
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy strict-origin-when-cross-origin;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 30d;
access_log off;
}
}保哥强调几个 SEO 友好的细节。第一,Strict-Transport-Security(HSTS)告诉浏览器一年内只用 HTTPS 访问本域名,浏览器会在本地直接拦下任何 http 请求并自动改写成 https,连一次 301 都省了,速度更快也更安全。第二,X-Frame-Options 和 X-Content-Type-Options 这些安全响应头是 Google 评估站点安全性的指标之一,加上没坏处。第三,静态资源的长缓存对 PageSpeed 评分提升非常明显,间接帮助 Core Web Vitals 各项指标。
FAQ 常见问题
Q1:我配置完之后浏览器还是显示「不安全」是为什么?
保哥见过最多的两种原因。第一种是「混合内容」——HTML 是 HTTPS 加载的,但页面里有 <img src="http://..."> 这样的硬编码 HTTP 资源,浏览器会因此降级 HTTPS 标识。解决办法是去 WordPress 或 Typecho 数据库里批量替换 http://example.com 为 https://example.com,可以用 wp-cli 的 wp search-replace 一行命令完成。第二种是 SSL 证书链不完整,常见于自签证书或没有附带中间证书的场景,用 openssl s_client -connect zhangwenbao.com:443 -showcerts 检查证书链是否完整,缺失中间证书的话需要把 fullchain.pem 而不是 cert.pem 配到 ssl_certificate 字段。
Q2:301 跳转配置好了但搜索引擎还在收录老的非主域名 URL?
这是正常现象,搜索引擎的索引更新有滞后期,一般需要几周到几个月不等才能完全合并。保哥的加速做法是三件套:第一,在 Google Search Console 和百度搜索资源平台都把主域名提交一遍并提交 sitemap.xml;第二,在站内文章里如果还有硬编码的非主域名链接,全部替换掉;第三,给老外链来源(特别是高权重站点)发邮件请他们更新链接。这三件事做完之后通常一个月内能看到明显改善。
Q3:网站开了 CDN 之后还需要这套 301 配置吗?
需要,但配置位置可能要调整。如果 CDN 节点是回源到你自己的 Nginx,那么 301 跳转规则放在 Nginx 上就行,CDN 会忠实转发 301 响应给用户。如果 CDN 厂商支持「边缘 301」功能(像 Cloudflare 的 Page Rules、阿里云 CDN 的回源 HTTP/HTTPS 设置),保哥的建议是把 301 跳转下沉到 CDN 边缘做,这样用户在最近的 CDN 节点就能拿到 301 响应,不用回源到主站,跳转速度从几百毫秒降到几十毫秒。两套方案选一就行,不要两边都开否则可能造成跳转链路重复。
Q4:301 跳转会不会影响到搜索引擎已有的排名?
短期会有几天到一两周的小幅波动,这是正常的「权重迁移期」,搜索引擎需要重新爬取新主域名并把旧 URL 的累计权重平滑迁移过去。保哥经手过的几十个站,迁移期波动幅度普遍在 10% 到 20% 之间,迁移完成后排名通常会回到原位甚至略有提升(因为合并了被分散的权重)。要把波动期影响降到最低,关键是:第一,跳转规则一次配对,不要反复改;第二,301 必须是单跳到位,不要跳两次;第三,提交新 sitemap 主动告诉搜索引擎;第四,避开搜索引擎大更新窗口期做切换。
写在最后
保哥的看法是:HTTPS 加 301 跳转是 SEO 的基础设施级配置,做好了用户和搜索引擎都察觉不到,做不好就会持续出血流量和权重。本文给的三段式 Nginx 配置是保哥自己 zhangwenbao.com 跑了好几年的实战版本,从 Nginx 1.10.1 到最新版 1.24 LTS 都兼容,按你自己的域名、网站根目录、SSL 证书路径替换三处地方就能直接上线。如果你是新站,建议直接采用文末那份现代化精简版,少写几十行配置而且更安全。配置文件这种东西保哥一贯主张「一次写到位」,反复折腾跳转规则不但增加运维负担,还会让搜索引擎反复经历权重重新分配的过程,是双输的事情。最后再提醒一句,改完配置一定要 nginx -t 检查语法,确认通过后再 systemctl reload nginx,永远不要直接 restart,否则一旦语法错误整个站都会下线。