很多站长在建站初期,都会有这样一个疑问:用户访问example.com和www.example.com到底该不该统一?要不要做301重定向?怎么做才最稳妥?这个问题从我做运维第一天起就一直有人问,前前后后帮朋友和自己处理过不下几十次域名统一跳转。今天就把我常用的5个方法一次性整理出来,从服务器配置到CDN后台再到代码层,每个方法都附上可以照着抄的配置和踩坑提醒。
先说结论:不带www的根域名和www子域名,在绝大多数场景下应该统一成一个主域名,而且推荐的做法是从根域名301跳到www域名。原因是根域名作为主站入口时,Cookie作用域、CDN缓存命中率、以及搜索引擎权重集中度都不如www子域名来得可控。当然也有人偏好反过来跳,这个纯属业务选择,原理完全一样。你只需要在里面挑一个适合你环境的方式照着配就行了。
1. 为什么建议做301重定向:根域名和www域名的区别
1.1 根域名、www域名、“外部域名”到底是什么关系
注册域名的时候,你会看到控制台里有“根域名”和“外部域名”这样的说法。根域名就是不带任何主机记录的裸域名,比如example.com;而www.example.com其实是它下面的一个主机记录,也就是一条A记录或者CNAME记录,本质上指向的可以跟根域名完全不同的服务器。
“外部域名”这个概念一般出现在DNS解析设置里,指的是将某个域名或子域名的解析目标指向当前域名之外的地址。比如你把www这条解析记录指向了CDN服务商提供的CNAME地址,那这个CNAME目标就是一个外部域名。很多新手在这里容易绕晕,其实一句话就能讲清楚:根域名是“主身份”,www是它的一个“马甲”,外部域名是“马甲指向的外面的地址”。你做301重定向,就是为了让其中一个“马甲”成为唯一对外的主入口。
1.2 为什么偏要用301而不是302,也不是直接不解析
301是永久重定向,搜索引擎会把原地址的权重、收录、外链关系整体转移到目标地址;302是临时重定向,权重不转移,等于告诉搜索引擎“你过阵子再来,这个地址还没失效”。如果你想让根域名的SEO价值全部沉到www主域名上,就必须用301,用302等于白忙活。
另外,有人图省事,干脆不解析根域名,只解析 www。这样访问example.com会直接报错,用户手输域名时大概率会输不带www的版本,体验直接崩掉。所以正规做法永远是:根域名和www都正常解析,然后在Web层做301跳转,统一入口。
1.3 统一域名后带来的实际收益
统一域名不只是解决“打开哪个都能访问”的体验问题,它会让后续运维省掉大量麻烦。比如Cookie作用域:你在根域名下种了Cookie,跳到www域名后就读取不到,用户登录状态直接丢失;再比如CDN缓存:如果你的图片资源有时从根域名加载、有时从www域名加载,CDN会认为是两个不同的站点,缓存命中率下降,成本还翻倍。
还有一个容易忽略的是统计口径。同一篇文章,搜索引擎会同时收录example.com/a和www.example.com/a两个地址,你的统计工具里就会看到两条几乎相同的数据,长期下去会严重污染数据分析结果。301跳转后,所有流量收敛到一个地址上,这些问题迎刃而解。
2. 方法一:Nginx服务器配置301跳转
2.1 Nginx下的两种写法对比
如果你用的是Nginx,最常见也最推荐的做法是单独建一个server块来拦截根域名的请求。有两种写法,一种是直接return,一种是rewrite。我强烈推荐return,因为性能比rewrite好,逻辑也更清晰。
server { listen 80; server_name example.com; return 301 https://www.example.com$request_uri; } server { listen 443 ssl; server_name example.com; # 这里同样要配置SSL证书 ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; return 301 https://www.example.com$request_uri; }这段配置的意思是:当HTTP请求的Host是example.com时,服务器直接返回301状态码,并在Location头里告诉浏览器“你要找的地址已经永久搬到了https://www.example.com后面跟上原来的路径和参数”。
2.2 为什么监听443的server块也需要跳转
很多人配完80端口的跳转就以为万事大吉了,结果发现用户从HTTPS访问根域名时并没有跳过去。原因很简单:如果你不给443端口也配置根域名的server块和证书,浏览器会先拒绝连接,根本走不到跳转那一步。
还有一个很容易踩的坑就是证书问题。假如你只买了一张包含www.example.com的证书,没有买根域名的证书,那么用户访问https://example.com时,即使你配置了跳转,浏览器也会先因为证书不匹配而弹出安全警告。要避免这个问题,要么把根域名和www都加进证书里(现在很多证书服务商都支持免费添加),要么干脆全部流量统一走HTTP层跳转,但那样就会损失HTTPS加密。我的建议是买证书的时候直接买包含根域名和www域名两个域名的通配或者多域名证书,一次性解决问题。
2.3 配置完成后如何验证
配置完Nginx后,先执行nginx -t检查语法,然后systemctl reload nginx平滑重载。验证跳转是否生效,用Linux下的curl命令最直观:
curl -I http://example.com如果配置正确,返回的响应头里会包含一行:
HTTP/1.1 301 Moved Permanently Location: https://www.example.com/看到301和完整的Location头,说明跳转已经生效。我习惯再顺手测一下带路径的跳转,比如curl -I http://example.com/some/page?foo=bar,确认$request_uri把路径和查询参数都带过去了。
3. 方法二:Apache服务器配置301跳转
3.1 通过.htaccess实现跳转
Apache的生态里,虚拟主机配置和.htaccess两种方式都可以实现301跳转。如果你用的是虚拟主机或者不方便改Apache主配置,.htaccess是最便捷的选择。在网站根目录的.htaccess文件里加这段:
RewriteEngine On RewriteCond %{HTTP_HOST} ^example\.com$ [NC] RewriteRule ^(.*)$ https://www.example.com/$1 [L,R=301]这段配置的含义是:当请求的HTTP_HOST完全匹配example.com(^表示开头,$表示结尾,[NC]表示忽略大小写)时,将请求永久重定向到https://www.example.com/后面跟原来的路径。末尾的[L,R=301]表示这是最后一个规则,并且返回301状态码。
如果不方便改.htaccess,也可以在虚拟主机配置里加同样的Rewrite规则,效果完全一样。需要注意的是,使用.htaccess的前提是Apache开启了mod_rewrite模块。我的经验是很多镜像站、面板站默认都开启了,但如果你改了规则没反应,第一步先确认这个模块有没有加载。
3.2 Apache配置中的常见坑
用Apache做301跳转,我遇到过最典型的问题有两个。第一个是RewriteCond条件写得太宽松,导致www域名本身也被跳转,陷入循环重定向。比如有的人写的是RewriteCond %{HTTP_HOST} !^www\.,这个条件本意是“只要不是www开头就跳转”,但如果服务器上还绑定了其他子域名,这些子域名也会被这个规则一并跳走。所以我更推荐精确匹配根域名,而不是排除www。
第二个问题是[R=301]写成了[R],或者只写了[L]。[R]默认是302临时重定向,对SEO有影响,所以一定要显式写成[R=301]。改完之后用浏览器无痕模式访问根域名测试,确认地址栏变成了www地址,再用浏览器的开发者工具看网络请求,确认状态码确实是301而不是302。
3.3 如何在虚拟主机里配置独立的跳转站点
如果你对服务器的控制权比较高,可以在/etc/httpd/conf.d/目录下新建一个虚拟主机配置文件,单独做一个跳转站点:
<VirtualHost *:80> ServerName example.com Redirect permanent / https://www.example.com/ </VirtualHost> <VirtualHost *:443> ServerName example.com SSLEngine on SSLCertificateFile /etc/pki/tls/certs/example.com.crt SSLCertificateKeyFile /etc/pki/tls/private/example.com.key Redirect permanent / https://www.example.com/ </VirtualHost>使用Redirect permanent指令比Rewrite规则更简单直接,因为它不需要启用mod_rewrite模块,Apache的核心模块里就支持这个指令。这里的/表示所有路径,也就是说不管用户访问根域名的哪个子路径,都会原样跳转到www域名的对应路径。比起.htaccess方案,这个方案的性能更好,因为Apache不需要对每个请求都走一遍Rewrite引擎。
4. 方法三:CDN或云服务商后台配置跳转
4.1 Cloudflare上的Redirect Rules配置
如果你用了Cloudflare,或者阿里云、腾讯云这类国内云厂商的CDN服务,完全可以不碰服务器,直接在CDN控制台完成301跳转。
拿Cloudflare来说,在控制台左侧菜单找到"Rules"下的"Redirect Rules",创建一条规则:
- 规则名称:根域名跳转www
- 请求URL匹配:Hostname等于
example.com - 行为:动态重定向
- 状态码:301
- 目标URL:
https://www.example.com${path}${query}
这里有两个变量值得解释一下:${path}会保留用户访问的路径,${query}会保留URL后面的查询参数。这样用户访问http://example.com/posts?id=1时会直接跳到https://www.example.com/posts?id=1,参数不丢。如果你用的是Stream类型简单重定向,注意有些时候它不支持动态拼接路径,默认只跳到首页,这就不符合我们预期了。
用过以后我的感受是,CDN层做跳转最大的好处是快:请求不会打到源站,直接在CDN边缘节点就返回301了,源站压力为零。而且配置改完后秒级生效,不像服务器配置还需要reload。所以如果你的站点已经接入了CDN,优先用这个方案。
4.2 国内云厂商的HTTPS证书与回源问题
国内云厂商的CDN在做这种跳转时,有一个额外的坑:如果你配置了自定义源站的HTTPS回源,同时又只给CDN上的www域名挂载了证书,那么用户访问https://example.com时,CDN节点找不到根域名对应的证书,会直接提示443端口握手失败。
解决办法是在CDN控制台的证书管理里,把根域名和www域名的证书都上传上去,或者动态签发一张包含两个域名的证书。如果条件不允许,就先把根域名的跳转放在未加密的HTTP层,但这样安全性会打折扣。
另外,国内云厂商的CDN产品大多提供“HTTP重定向”或“访问控制”这类功能,原理和Cloudflare类似,核心就是设置:源站为根域名、目标为www域名、状态码为301。配置入口可能会有点差异,但核心选项就那几个,对照着找就行。
4.3 在DNS层面做URL转发可行吗
不少域名注册商(比如阿里云万网、Namecheap)都提供“URL转发”功能,可以把一个域名直接转发到另一个域名。这个功能看起来很方便,不用配服务器不用配CDN,后台填一下就能用。
但我的建议是:不到万不得已,不要用DNS层面的转发来做这个需求。原因有三点:
- 很多注册商的URL转发不支持HTTPS目标地址,或者不支持全路径透传,跳过去经常只是跳到了首页。
- 部分注册商的转发是302或者用iframe框架页实现的,HTTP状态码不对,对SEO有不小的影响。
- DNS层面转发依赖注册商的转发服务器,可用性和跳转速度不受你控制,如果转发服务器挂了,你的域名就完全打不开了。
综合来看,如果你只是想临时应急,DNS转发可以顶一下;但从长期稳定性和专业角度,还是优先把跳转下沉到服务器或CDN层。
5. 方法四:应用框架与代码层重定向
5.1 WordPress后台和PHP代码的实现方式
如果你的站点是WordPress搭建的,其实都不用写代码。登录后台,在“设置-常规”里,把“WordPress地址(URL)”和“站点地址(URL)”都填成https://www.example.com,然后在服务器或CDN层把根域名跳转到www域名即可。
但如果你用的是定制开发的应用,或者WordPress里的某些缓存插件直接输出了内容,那就需要在代码层兜底。PHP和Python都只能做“尽力而为”的跳转,因为代码执行的前提是请求已经到达了应用服务器,如果连服务都没起来,代码肯定跑不了。所以在Nginx/Apache层先拦一道,代码层作为双保险,是我推荐的做法。
5.2 代码层的通用写法示例
以PHP为例,在入口文件的顶部加这样一段逻辑:
if ($_SERVER['HTTP_HOST'] === 'example.com') { header('Location: https://www.example.com' . $_SERVER['REQUEST_URI'], true, 301); exit; }注意这里用header()函数时必须把第三个参数显式传301,否则默认返回302。$_SERVER['REQUEST_URI']会包含完整路径和查询参数,保证跳转后不丢失用户原本要访问的内容。
Python Flask框架下可以写一个before_request钩子:
from flask import Flask, redirect, request app = Flask(__name__) @app.before_request def redirect_root(): if request.host == 'example.com': return redirect('https://www.example.com' + request.full_path, code=301)这里的代码逻辑都很简单,唯一要提醒的是:代码层判断的“根域名”一定要写得足够精确,避免误伤其他子域名。比如你判断条件写成了'example.com' in request.host,那么sub.example.com也会被跳走,这是个隐藏炸弹。
5.3 为什么说代码层只适合做兜底
代码层重定向有一个天然劣势:应用框架启动是有开销的。用户访问根域名时,请求要先经过Web服务器、PHP-FPM进程、应用初始化的完整链路,然后在代码里才返回一个301。这意味着每次跳转都要白白消耗一次服务器资源,如果是高并发场景,这个损耗会被放大。
更严重的是,如果代码因为某种原因报错或者执行超时,跳转逻辑就可能失效,用户访问根域名会看到错误页面,这是非常影响体验的事。所以我个人习惯的做法是:服务器层面做主力跳转,代码层只作为开发和本地环境临时用,线上坚决不依赖。
5.4 局域网场景下建站是否也需要这个跳转
单独说一个场景:你在局域网内用Linux服务器搭了一个www服务,比如内网办公系统。这种情况下,你同样需要处理根域名和www域名的关系吗?
我的建议是看使用习惯。如果内网用户习惯直接敲http://server.local这种无www的地址访问,而你希望统一用http://www.server.local,那同样可以按前面的Nginx方法配置跳转。不过局域网的DNS解析通常是内网自建的,你得确保根域名和www都解析到了同一台内网服务器。
局域网跳转有个小坑:如果内网DNS只解析了server.local而没解析www.server.local,那么跳转到www.server.local时会提示找不到服务器。所以配置跳转之前,一定先确认两条解析记录都存在。另外,内网环境如果没上HTTPS,跳转目标写http://开头就好,千万别照搬外网配置写了https://,否则会因为证书不匹配导致访问直接失败。
6. 方法五:结合HTTPS与HSTS的整体跳转方案
6.1 HTTPS和301的先后顺序
现在的站点基本都是HTTPS加密访问了,这就产生了一个先后顺序问题:是先做HTTP到HTTPS的跳转,还是先做根域名到www的跳转?这两个跳转叠加在一起,顺序如果错了,可能会多一次跳转,影响访问速度。
推荐的顺序是:先做HTTPS跳转,再做域名统一。也就是说,当用户访问http://example.com时,理想的跳转路径应该是:
http://example.com → 301 → https://www.example.com在Nginx里实现这个效果,只需要在80端口监听根域名的server块里直接返回最终的https地址即可,不需要先跳到http://www.example.com再跳https://www.example.com。配置方法就是前面2.1节写的那样,一次跳转到位。
6.2 HSTS对根域名跳转的影响
HSTS(HTTP严格传输安全)是一种让浏览器自动使用HTTPS访问的机制。如果你的站点已经启用了HSTS并且includeSubDomains参数,那么浏览器在访问你的根域名时,会直接强制用HTTPS协议,不再走HTTP。
这就带来一个连锁问题:如果你只在www.example.com上启用了HSTS,那么即使你配置了根域名的301跳转,当浏览器直接访问https://example.com时,如果根域名的证书不合法,用户依然会看到警告页面。解决方式只能是确保证书覆盖根域名,或者在根域名的响应头里也加上HSTS。
我个人在处理HSTS和跳转关系时,遵循一个原则:让跳转规则优先,HSTS头在跳转后的目标站点上统一加。也就是说,不希望在根域名的server块里配置HSTS头,只让它输出301跳转,这样既保证安全又简化管理。
6.3 多级域名的清理与兜底
在涉及301跳转到www主域名的过程中,还有一个需要留意的点:如果你的服务器上同时解析了m.example.com、api.example.com这类子域名,而这些子域名并不打算跳转到www主域名,那么跳转规则一定要写精确。最稳妥的方法是“允许白名单,拒绝黑名单”:先列出要跳转的域名列表,只对列表中的域名做匹配,其余一律不动。
# 只对根域名做跳转,不影响其他子域名 if ($host = 'example.com') { return 301 https://www.example.com$request_uri; }Nginx里用if配合精确匹配相比正则匹配来说更安全,不容易误伤。当然,如果服务器上只有根域名和一个www,那就无所谓了,随便哪种写法都行。真正要小心的是那种一台服务器上挂了十几个站点的情况,一个正则写错,全站遭殃。
7. 常见问题与排查技巧实录
7.1 配置完了还是没跳转,怎么排查
每次帮人排查“根域名不跳转”的问题,我都会按下面顺序从头到尾检查一遍,这个顺序帮我省了不少时间:
- 检查DNS解析:根域名和www是否真的都解析到了当前服务器?用
dig example.com和dig www.example.com分别确认。 - 检查Web服务器有没有生效:
nginx -t或apachectl configtest过一下语法,确认配置加载成功。 - 确认访问的是不是走了CDN缓存:如果你的域名挂在CDN后面,CDN节点可能会缓存旧的HTTP响应,导致你改了源站配置也没用,此时需要刷新CDN缓存。
- 检查是否被其他跳转规则抢先:有的服务器上同时有HTTP→HTTPS跳转、子域名跳转等多条规则,顺序不同会导致结果不同。
- 用curl分步测试:
curl -I http://example.com看第一步跳转,再看curl -I https://www.example.com确认目标站正常。
7.2 跳转后出现死循环或者带不上路径
死循环是重定向配置里最恶心的问题。根域名跳到www,然后www又判断“不是根域名,不跳”,这是正常配置。但如果你在www域名的server块里也写了一条“不是www就跳www”的规则,那就会陷入死循环:根域名跳www,www自己命中了规则又跳www,无限循环。
排查死循环最快的方法是curl -I多打几次看Location头。如果你发现每次返回的Location都一样,而地址根本没变化,基本可以断定是规则自解释了。解决方式是只在根域名的server块里写跳转,不要在www的server块里做任何判断。
7.3 一条排查速查表
| 症状 | 可能原因 | 排查动作 |
|---|---|---|
| 根域名可以访问但不跳转 | 服务器配置未加载,或DNS解析没生效 | 检查nginx -t和dig解析记录 |
| 访问HTTPS根域名报证书错误 | 根域名没有对应的SSL证书 | 申请包含根域名的多域名证书或通配符证书 |
| 跳转后地址栏变成www但无限刷新 | 规则自匹配导致循环重定向 | 检查www域名server块里是否有跳转规则 |
| 根域名跳转后路径丢失 | 跳转目标没有拼接$request_uri | 改用完整带$request_uri的写法 |
| 某些子域名也被跳到了www | 跳转规则写得太宽泛 | 改用精确匹配根域名,不使用排除式写法 |
| 改了配置后不生效 | Web服务器未reload或存在CDN缓存 | 执行systemctl reload nginx,刷新CDN缓存 |
| 全部域名都打不开 | 服务器绑定域名配置出错 | 检查监听端口和server_name是否正确 |
7.4 我的最终建议
结合这几年的经验,如果你问我个人最推荐哪个方法,我的回答是:能用CDN跳转就用CDN,没有CDN就用Nginx。前者操作简单、生效快、对源站无压力;后者可控性强、适合各种复杂环境、不依赖第三方服务。Apache其次,代码层只能算保底。
另外分享一个习惯:每次改完跳转配置,我都会用无痕窗口手动访问一次http://example.com/任意路径?foo=bar,确认三件事:状态码是301、跳转带上了路径和参数、最终地址是HTTPS开头的www域名。三件事全部满足,我才会认为这次配置是合格的。只要这个流程跑通了,后面不管用户从哪个入口进,流量都会规规矩矩地收敛到主域名上,后续运维会轻松非常多。