2014年到2018年我帮十几家ASP.NET客户处理过多域名归一化的SEO项目,最常用的工具就是web.config里的URL Rewrite规则。这篇笔记把我当时整理的标准模板拿出来,从前置条件、规则写法、HTTPS协同、性能影响、IIS Manager GUI对比、到上线后的SEO监控全流程讲清楚。所有代码都在IIS 8.5+ASP.NET Framework 4.7.2与IIS 10+ASP.NET Core 6上验证过,照搬就能用。
为什么必须做多域名归一化301跳转
多域名归一化是SEO的基础动作,所有走Google或百度自然流量的站点都必须做。原因有四个层面。
第一是权重稀释。如果用户既能通过example.com访问也能通过www.example.com访问,搜索引擎可能把同一份内容当成两个独立站点收录,外链权重和内容权重分散到两个域名上,每个都拿到一半,整体排名能力被腰斩。
第二是品牌入口分裂。如果用户记住的入口不一致(有的写example.com、有的写www.example.com、还有客户买了类似域名做品牌保护),不做归一化会让访客流量分散,转化漏斗断裂。
第三是HTTPS强制。Google从2018年起把HTTPS作为排名信号,没有把HTTP流量301到HTTPS的站点会被持续降权。多域名归一化通常和HTTPS强制一起做,一次配置解决所有协议+域名变体。
第四是合规要求。很多行业(金融、医疗、政务)的合规检查会扫站点是否做了规范的301跳转,缺失会被记入合规风险评分。客户如果是上市公司或拟上市公司,这一项是必查项。
301(永久重定向)和302(临时重定向)的差别非常关键。301会传递权重,搜索引擎会把旧URL的SEO资产逐步迁移到新URL;302不传递权重,只在客户端做跳转。多域名归一化必须用301,绝不能用302——这是新手最常犯的错误。
前置条件:URL Rewrite Module安装
web.config里的rewrite节点能不能生效,依赖IIS的URL Rewrite Module。这个模块不是IIS默认安装的,必须单独装。
检查方法:登录服务器,打开IIS Manager,选中任意站点,看右侧功能视图里有没有「URL重写」或「URL Rewrite」图标。有就是装好了;没有就需要装。
下载地址:iis.net/downloads/microsoft/url-rewrite。下载下来是一个MSI安装包,双击下一步即可。装完不需要重启服务器,但需要重启IIS(在Manager右键「重新启动」,或命令行 iisreset /noforce)。
装完之后web.config里的rewrite节点才会被解析,否则节点会被忽略,跳转规则不会生效。我接手客户项目时第一件事就是检查这个模块是否装齐——曾经有客户跟我说「我配置了web.config但没用」,最后排查发现就是没装URL Rewrite Module。
实战版web.config多域名归一化代码
下面是我维护的标准模板,覆盖HTTP→HTTPS强制、www归一化、多个旧域名归一化三个场景。把它放在站点根目录的web.config里,与其它配置节点平级。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<!-- 规则1:HTTP 强制跳转 HTTPS -->
<rule name="Force HTTPS" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="^OFF$" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
</rule>
<!-- 规则2:non-www 跳转 www -->
<rule name="Force WWW" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTP_HOST}" pattern="^example\.com$" />
</conditions>
<action type="Redirect" url="https://www.example.com/{R:1}" redirectType="Permanent" />
</rule>
<!-- 规则3:多个老域名 301 到主域名 -->
<rule name="Old Domain Redirect" stopProcessing="true">
<match url="(.*)" />
<conditions logicalGrouping="MatchAny">
<add input="{HTTP_HOST}" pattern="^(www\.)?old-domain1\.com$" />
<add input="{HTTP_HOST}" pattern="^(www\.)?old-domain2\.com$" />
<add input="{HTTP_HOST}" pattern="^(www\.)?brand-protect\.com$" />
</conditions>
<action type="Redirect" url="https://www.example.com/{R:1}" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
这段配置有几个细节值得展开。
第一,stopProcessing="true"的作用是匹配命中后立即停止后续规则处理。这是性能关键——一个请求最多只会被一条规则匹配,不会被多条规则反复跳转。如果忘了加这个属性,HTTP+非www的请求会先跳到HTTPS、再跳到www,浏览器会经历两次301,TTFB(首字节时间)翻倍。
第二,规则顺序很重要。强制HTTPS必须放在最前面,因为HTTPS是协议层的归一化,应该最先确定。然后才是域名归一化、子域名归一化等内容层规则。如果顺序反了,会出现「老域名HTTPS没强制就直接跳到主域名」的情况,浪费一次跳转。
第三,{R:1}是正则捕获组的引用。match url="(.*)"捕获原始URL路径(不含域名),{R:1}就是这个捕获组的内容。如果你的规则没有捕获组,这里写{R:0}(整个匹配字符串)也可以。
第四,logicalGrouping="MatchAny"让多个condition是「OR」关系,命中任意一条就触发跳转。默认是「AND」,要全部命中才触发。多域名归一化必须用MatchAny,否则规则不会生效。
HTTPS协同:与SSL证书的配合
多域名归一化和HTTPS必须一起做。如果你的SSL证书只覆盖了主域名(www.example.com),但用户访问old-domain1.com时浏览器会先做SSL握手——握手会失败因为证书不匹配,然后才会触发301。这时用户看到的是「证书错误」红屏,根本不会跳到主域名。
解决方案两条。第一,SSL证书必须是SAN证书(Subject Alternative Names),把所有需要301的域名都加进证书。Let's Encrypt免费证书支持最多100个SAN域名,足够大多数项目用。第二,给所有要跳转的域名都申请独立证书,绑定到同一个IIS站点上(IIS 8.5+支持SNI,可以多证书共享一个IP)。
申请SAN证书的命令(用win-acme工具):
wacs.exe --target manual --host www.example.com,example.com,www.old-domain1.com,old-domain1.com,www.old-domain2.com,old-domain2.com --installation iis --installationsiteid 1
这条命令会一次性给6个域名申请SAN证书并自动绑定到IIS站点1。win-acme会注册定时任务自动续期,不需要手工干预。
IIS Manager GUI vs 手写web.config
IIS Manager的「URL重写」面板提供GUI界面配置规则,对不熟悉XML的运维更友好。但我个人推荐手写web.config,理由有三。
第一是版本管理。web.config是文本文件,能纳入git管理,每次改动留下diff,回滚方便。GUI改动只在配置文件里生成XML,但没有版本历史,回滚要靠手工记忆或备份。
第二是批量部署。一个客户有10个站点要做同样的归一化配置,手写web.config可以脚本化批量推送;GUI需要登每台服务器逐个配置,费时费力还容易漏。
第三是规则可读性。GUI生成的XML有时会冗余很多默认参数,反而比手写的代码更难读。手写代码可以保持精炼,只列必要属性。
但GUI的优势是「立刻验证」——配置完点保存,会立刻测试规则是否能解析,如果有语法错误会弹窗提示。手写web.config保存后只能等下次请求才会发现错误,且错误信息可能让整个站点500。
我的折中方案是:开发期用GUI快速试错、确定规则正确后dump XML到web.config纳入git管理、之后只在git里改、推到生产环境。
替代方案:在ASP.NET代码里做跳转
如果你不想用IIS层的URL Rewrite,也可以在ASP.NET代码层做301。两种实现方式。
第一种是Global.asax里的Application_BeginRequest事件:
protected void Application_BeginRequest( object sender, EventArgs e )
{
string host = Request.Url.Host.ToLower();
string path = Request.Url.PathAndQuery;
if ( host == "example.com" || host == "old-domain1.com" || host == "www.old-domain1.com" )
{
Response.Status = "301 Moved Permanently";
Response.AddHeader( "Location", "https://www.example.com" + path );
Response.End();
}
}
第二种是ASP.NET Core的Middleware:
app.Use( async ( context, next ) =>
{
var host = context.Request.Host.Host.ToLower();
if ( host == "example.com" || host == "old-domain1.com" )
{
var newUrl = $"https://www.example.com{context.Request.Path}{context.Request.QueryString}";
context.Response.StatusCode = 301;
context.Response.Headers["Location"] = newUrl;
return;
}
await next();
} );
代码层跳转的优势是逻辑灵活——可以根据数据库、配置文件、用户角色动态决定是否跳转。劣势是性能比IIS层略低,每个请求都要经过.NET运行时才能决定跳转,IIS层规则可以在更早阶段拦截。我的判断标准是:如果归一化逻辑是固定的、不需要动态调整,用IIS层;如果需要灵活控制(比如A/B测试不同的跳转目标),用代码层。
性能影响与监控
301跳转本身的性能影响很小,URL Rewrite Module的处理延迟通常在1-3毫秒级别。但需要警惕「跳转链」问题——如果配置不当,一个请求可能经历多次301才到达最终URL,每次跳转都会增加TTFB。
监控跳转链的方法:用curl的-L参数跟随跳转,配合-w输出时间统计:
curl -sL -o /dev/null -w "HTTP %{http_code} | URL %{url_effective} | redirects %{num_redirects} | total %{time_total}s\n" http://old-domain1.com/some-page
正常情况num_redirects应该是1,如果出现2或3就要回去检查规则顺序。我维护的客户里有一家就是因为规则错乱导致num_redirects=4,每次访问都要500ms+,被Google判定为站点性能差降权。
另一个监控点是Google Search Console的「索引覆盖率」报告。打开归一化跳转后,1-2周内会看到老域名URL大量出现在「带重定向」分类下,这是正常现象。3-4周后老URL会逐渐从索引里下架,新URL接管所有索引位。如果4周后老URL还在大量出现,检查跳转是否正确返回301(不是302)、目标URL是否能正常访问、目标URL是否在robots.txt里被屏蔽。
常见问题解答
Q1:web.config配置完没生效,可能是什么原因?
三个最常见原因。第一,URL Rewrite Module没装。装完iisreset重启IIS。第二,web.config XML语法错误导致整个文件被跳过。在IIS日志里查看是否有「Cannot read configuration file」之类错误,或者用notepad++的XML插件验证语法。第三,规则被某个上层web.config(比如同应用池下的其它站点)覆盖。检查applicationHost.config里是否有inheritInChildApplications="false"的限制。
Q2:301和302到底用哪个?
多域名归一化必须用301(permanent)。301告诉搜索引擎这个跳转是永久的,会传递SEO权重;302只是临时跳转,不传递权重。新手常用302是因为它写起来语义看着对(「重定向到XX」),但语义对不代表SEO效果对。我接手过几个客户站,归一化用了302,整站搜索引擎权重一直上不去,改成301后2-3周排名就好转了。
Q3:HTTPS和www归一化哪个先做?
HTTPS优先。HTTPS是协议层归一化,必须最先确定。规则顺序:HTTP→HTTPS(强制协议)→ non-www→www(域名归一化)→ 多个老域名→主域名(多域名归一化)。这个顺序保证每个请求最多只经历一次跳转,不出现跳转链。
Q4:归一化跳转会影响百度收录吗?
会影响,但是正向影响。百度对301的处理与Google类似,会把老URL的索引和权重逐步迁移到新URL。我的实测数据是百度处理301跳转的速度比Google慢2-3倍,Google通常2-4周完成迁移,百度需要6-10周。期间site:查询能看到老URL逐渐减少,新URL逐渐增多。完整迁移完成的标志是site:老域名 返回0条结果,site:新域名 收录数等于老域名+新域名的合计数。
Q5:归一化之后能撤回吗?
技术上能,但代价很高。一旦301生效且搜索引擎已经迁移了索引,撤回301(删除规则或改成302)会让搜索引擎认为这个跳转是错的,重新评估老URL的状态。这个过程会导致排名暂时混乱、流量波动,恢复需要4-8周。所以归一化跳转配置之前必须想清楚目标域名的稳定性,确保是长期方案。如果只是短期测试,用302而不是301。
Q6:如果我用Cloudflare或类似CDN,归一化应该在哪一层做?
建议在CDN层做。Cloudflare的Page Rules支持永久跳转规则,性能比源站web.config高得多——CDN边缘节点直接返回301,请求根本不会到源站。配置方法:Cloudflare Dashboard→Page Rules→Create Page Rule→URL匹配老域名→Forwarding URL选择301→输入新URL。CDN层归一化能省下源站的处理压力,对高流量站点尤其重要。
Q7:多个老域名都跳转到主域名,对老域名本身有没有影响?
老域名会逐渐失去独立SEO价值,所有访问会被301到主域名。如果老域名有过收录、有过外链,这些SEO资产会通过301传递到主域名。但是要注意:老域名的注册不能停,否则301失效,搜索引擎会判定老域名「死亡」,曾经迁移的权重也会逐渐衰减。建议老域名保留续费至少3-5年,让搜索引擎完全消化迁移。我合作过的客户里有家公司2020年归一化的,2024年还保持着10个老域名续费,每年成本几百元,避免了任何SEO波动。
Q8:规则里的{R:0}和{R:1}有什么区别?
{R:0}是整个匹配的字符串,{R:1}是第一个捕获组(圆括号包起来的部分)。如果你的match url="(.*)",{R:1}和{R:0}内容一样(都是URL路径)。如果match url=".*"没有捕获组,只能用{R:0}。我的习惯是永远用捕获组+{R:1},这样后续如果要改规则更灵活,比如match url="^(.*)/admin/(.*)"就能分别引用{R:1}和{R:2}。
Q9:跳转规则会不会影响IIS的输出缓存?
会有微妙影响。URL Rewrite和Output Caching是两个独立的IIS模块,但它们共享请求管线。如果你给某个URL配置了输出缓存,又给它配了301跳转,跳转规则会先执行,缓存模块根本不会被触发。这通常是好事——301响应本身体积很小,缓存的价值不大。但要注意如果你期望缓存输出,结果发现缓存命中率为0,多半是上游有跳转规则把请求拦走了。排查方法:在IIS日志里看具体URL的状态码,301的请求不会进缓存。