# 保哥笔记 — Windows > 本分片含 4 篇文章,按发布日期倒序。全部分片索引见 https://zhangwenbao.com/llms-full.md **站点**:https://zhangwenbao.com/ **分类**:Windows **生成**:2026-06-04 23:09:29 CST --- ## ASP.NET多域名301跳转:web.config实战代码 - URL:https://zhangwenbao.com/web-config-asp-net-multiple-domain-redirect.html - 分类:Windows - 发布:2020-11-20 | 更新:2026-05-16 - 摘要:ASP.NET多域名要做301归一,web.config里有不少细节。本文拆解stopProcessing、logicalGrouping、捕获组等关键属性,对比IIS图形界面与手写XML的优劣,再给ASP.NET Core中间件方案、SAN证书申请命令、跳转链监控脚本和Cloudflare的CDN层归一化。 - 关键词:IIS,重定向,301重定向,ASP > **TLDR**:摘要:ASP.NET多域名要做301归一,web.config里有不少细节。本文先讲为什么必须归一,给出URL Rewrite模块的前置安装、实战版的多域名归一化web.config代码、与SSL证书的HTTPS协同,再对比IIS图形界面与手写web.config的优劣、在ASP.NET代码里做跳转的替代方案,以及性能影响与跳转链监控。 > 摘要:ASP.NET多域名要做301归一,web.config里有不少细节。本文先讲为什么必须归一,给出URL Rewrite模块的前置安装、实战版的多域名归一化web.config代码、与SSL证书的HTTPS协同,再对比IIS图形界面与手写web.config的优劣、在ASP.NET代码里做跳转的替代方案,以及性能影响与跳转链监控。 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跳转 (https://zhangwenbao.com/301-url-redirection-http-jumps-to-https-and-https-jumps-to-http.html),缺失会被记入合规风险评分。客户如果是上市公司或拟上市公司,这一项是必查项。 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里,与其它配置节点平级。 这段配置有几个细节值得展开。 第一,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测试 (https://zhangwenbao.com/ab-testing-ctr-conversion-optimization.html)不同的跳转目标),用代码层。 ## 性能影响与监控 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 (https://zhangwenbao.com/gsc-regex-mine-ai-search-prompts-guide.html)的“索引覆盖率”报告。打开归一化跳转后,1-2周内会看到老域名URL大量出现在“带重定向”分类下,这是正常现象。3-4周后老URL会逐渐从索引里下架,新URL接管所有索引位。如果4周后老URL还在大量出现,检查跳转是否正确返回301(不是302)、目标URL是否能正常访问、目标URL是否在robots.txt (https://zhangwenbao.com/tools/robots-generator.php)里被屏蔽。 ## 常见问题解答 ## 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的请求不会进缓存。 ## 权威参考资料 ## IIS强制HTTPS跳转:URL Rewrite5步实战 - URL:https://zhangwenbao.com/iis7-set-http-to-https-redirect.html - 分类:Windows - 发布:2017-03-01 | 更新:2026-05-16 - 摘要:覆盖IIS 7.5到IIS 10各版本HTTP跳HTTPS 301配置:URL Rewrite Module 32/64位安装包、msiexec无人值守批量分发、web.config XML完整规则、ApacheBench四方案性能对比、HSTS preload提交流程、查询字符串保留与POST跳转细节。 - 关键词:https,IIS,重定向,HSTS,URL Rewrite > **TLDR**:摘要:网站开了SSL就该把HTTP强制301跳到HTTPS。本文覆盖IIS 7.5到10各版本——URL Rewrite模块安装、用msiexec无人值守批量分发、web.config的完整XML规则、放文件与重启IIS的正确姿势,再讲HSTS与preload提交、查询字符串保留与POST跳转细节、各版本配置差异、与宝塔和Nginx和Apache方案的对比,以及日志分析与监控。 > 摘要:网站开了SSL就该把HTTP强制301跳到HTTPS。本文覆盖IIS 7.5到10各版本——URL Rewrite模块安装、用msiexec无人值守批量分发、web.config的完整XML规则、放文件与重启IIS的正确姿势,再讲HSTS与preload提交、查询字符串保留与POST跳转细节、各版本配置差异、与宝塔和Nginx和Apache方案的对比,以及日志分析与监控。 大家好,我是保哥。这几年我先后帮十几位站长把Windows Server上的老站点从HTTP升级到HTTPS,几乎每一次都会被同一个问题卡住:证书装好了、443端口也通了,但访问 http://域名 依然能打开未加密版本,搜索引擎抓回的还是HTTP链接。问题的根源是IIS默认不会把旧协议跳到新协议,必须手动加一条301重定向 (https://zhangwenbao.com/google-search-console-404-error-fix-guide.html)规则。下面这篇笔记,是我在IIS7、IIS7.5、IIS8.5、IIS10四个版本上反复验证过的做法,从下载模块、写web.config、到调试踩坑都写全,方便我自己以后照抄,也希望对你有用。 ## 为什么必须做HTTP到HTTPS的301跳转 很多人以为只要在服务器上绑定了SSL证书就大功告成,其实远远不够。我处理过的几个站点出过这些问题: - 百度站长平台抓取诊断显示同一篇文章既有HTTP索引又有HTTPS索引,权重被分成了两份。 - Chrome 84之后对纯HTTP站点直接打"不安全"标签,跳出率明显升高。 - 已经备案过HTTPS的站点如果没做强跳,混合内容(mixed content)告警会让浏览器拦截JS、CSS。 - 微信内置浏览器对未跳转到HTTPS的页面会做拦截提示,影响转化。 - 支付宝、微信支付的回调URL在 2021 年之后强制要求 HTTPS,纯 HTTP 站点连接入支付都做不了。 - HTTP/2、HTTP/3、Brotli 压缩都依赖 HTTPS,没有强跳的站点享受不到这几个性能加成。 做301永久重定向可以一次解决以上问题。301状态码会让搜索引擎把旧URL的权重平滑迁移到新URL,对SEO友好;同时浏览器会缓存这条跳转,第二次访问就直接走HTTPS,不再多一次请求。这是Google与百度官方文档都推荐的做法。 ## 301 vs 302 vs HSTS:三种"强制 HTTPS"的对比 机制 | 是否传 SEO 权重 | 是否减少首跳延迟 | 是否需要浏览器配合 | 302 临时跳转 | 否 | 否,每次访问都跳 | 否 | 301 永久跳转 | 是 | 浏览器缓存后第二次起免跳 | 否 | HSTS | 不直接相关 | 是,浏览器在头部缓存有效期内自动 HTTPS | 是,需 max-age 内浏览器记忆 | HSTS Preload List | 不直接相关 | 是,首次访问就 HTTPS | 是,需提交到 Chromium 列表 | 实战里 301 + HSTS 是最佳组合:301 让所有客户端都能正确跳转、传递权重;HSTS 让回头访客省掉那一次 30x 跳转延迟。下面会把两者都讲清楚怎么配。 ## 准备工作:URL Rewrite模块下载与安装 IIS7自带的功能里没有URL重写,必须手动安装微软出品的URL Rewrite Module。我个人比较喜欢从微软官方下载链接拿安装包,避免被第三方站点二次打包带广告。下面是我目前还在用的两个直链,注意看自己服务器是32位还是64位: - 32位安装包:download.microsoft.com/download/4/9/C/49CD28DB-4AA6-4A51-9437-AA001221F606/rewrite_x86_zh-CN.msi - 64位安装包:download.microsoft.com/download/4/E/7/4E7ECE9A-DF55-4F90-A354-B497072BDE0A/rewrite_x64_zh-CN.msi 判断系统位数最快的办法是在PowerShell里跑一句: [Environment]::Is64BitOperatingSystem 返回True就装x64那个MSI;返回False装x86即可。安装过程是傻瓜式的,一路下一步即可。安装完之后,打开IIS管理器,选中你的站点,右侧应该多出一个"URL重写"的图标。如果没有出现,重启一下IIS管理器或者执行iisreset命令再看一次。 我有一次在Windows Server 2008 R2上安装失败,提示"安装程序无法继续"。后来发现是缺少Web Platform Installer依赖,把WebPI装上之后再装URL Rewrite就过了。如果你也碰到这种情况,先把WebPI补齐。 ## 无人值守批量安装(多服务器场景) 如果你像我一样维护一组同型号的 Windows Server(比如某客户在三个机房各有 4 台 Web,共 12 台),用 GUI 一台一台装效率太低。MSI 安装包支持静默安装,命令是: msiexec /i rewrite_x64_zh-CN.msi /qn /norestart /L*v rewrite_install.log 结合 PowerShell Remoting 可以一次性铺到 12 台机器: $servers = @('web01','web02','web03','web04','web05','web06','web07','web08','web09','web10','web11','web12') Invoke-Command -ComputerName $servers -ScriptBlock { $msi = '\\fileserver\share\rewrite_x64_zh-CN.msi' Start-Process msiexec.exe -ArgumentList "/i `"$msi`" /qn /norestart" -Wait Get-WindowsFeature Web-Url-Auth | Select-Object Name, InstallState } install.log 会留在每台机器 C:\Windows\Temp\rewrite_install.log,万一某台装失败可以单独捞回来看。 ## 编写web.config实现301跳转 URL Rewrite模块装好后,跳转规则可以通过IIS图形界面点选生成,也可以直接写web.config。我个人偏爱后者,因为可以版本化、批量复制到多个站点。下面是我用了多年的最简模板: 几个关键参数说明一下,避免你照抄之后改不动: - match url='(.*)' 匹配所有请求路径,括号里捕获原始URL,用 {R:1} 引用。 - {HTTPS} 是IIS内置变量,HTTP请求时值为OFF,HTTPS请求时值为ON。判断 ^OFF$ 才能避免死循环。 - redirectType='Permanent' 对应HTTP 301状态码。如果暂时不想让搜索引擎更新索引,可以改成Found(302),但不推荐长期这么用。 - {HTTP_HOST} 自动取请求里的域名,主域和www子域共用一份配置都没问题。 - stopProcessing='true' 命中后不再继续匹配后面的规则,性能略好且不会跟其它规则打架。 把上面这段保存成web.config,编码必须是UTF-8无BOM,放到网站根目录。如果根目录已经有web.config,把 rewrite 节点合并进去即可,不要直接覆盖。 ## 组合多条规则:强制 www、HTTP 跳 HTTPS、移除尾斜杠 真实项目里经常需要把几条规则放在一起,顺序很有讲究:先把无 www 跳到 www,再把 HTTP 跳到 HTTPS。这样可以避免被 301 跳两次(先 http://example.com 跳 http://www.example.com,再跳 https://www.example.com,浪费一次 RTT)。优化后的写法是直接一步跳到目标: logicalGrouping='MatchAny' 等价于 OR——任意一个条件命中就跳。把所有"不是 https + www"的情形一次跳到最终形态,避免链式 301。 ## 放置文件与重启IIS的正确姿势 文件放好之后不一定立刻生效,我习惯按下面这个顺序检查一次: - 在IIS管理器里选中站点,点"查看应用程序",确认根目录路径与放置web.config的目录一致。 - 双击右侧的"URL重写"图标,确认能看到HTTP to HTTPS redirect这条规则;如果看不到,多半是web.config写错被IIS忽略。 - 命令行执行iisreset(这条命令需要管理员权限的cmd窗口): iisreset /noforce - 用curl在另一台机器上测试响应头: curl -I http://你的域名/ 正常输出应该包含: HTTP/1.1 301 Moved Permanently Location: https://你的域名/ - 浏览器访问 http://你的域名/path/to/page,确认地址栏自动跳到 https://你的域名/path/to/page,路径完整保留。 如果curl看到的是200而不是301,先检查站点是否同时绑定了80和443端口;再看web.config是否真的被IIS读到了。我之前犯过一个低级错误:把文件命名成了web.config.txt,Windows默认隐藏扩展名,肉眼看不出来。 ## 用 curl 做"端到端"验证清单 我每次上线 HTTPS 跳转都会跑一遍下面这 6 条 curl,确认链路无瑕疵: # 1. 基础 80→443 跳转 curl -I http://example.com/ # 2. 带路径跳转 curl -I http://example.com/products/123.html # 3. 带查询字符串跳转 curl -I 'http://example.com/search?q=test&page=2' # 4. www 跳转 curl -I http://example.com/ # 应跳 https://www.example.com/ # 5. HSTS 头是否带回来 curl -sI https://www.example.com/ | findstr /i 'strict-transport-security' # 6. 检查证书链 curl -v https://www.example.com/ 2>&1 | findstr /i 'subject\|issuer\|verify' 每条都过了再上线,从来没翻过车。第 3 条特别容易出问题——很多人写规则时忘了 {R:1} 不会带查询字符串,得显式加 {QUERY_STRING} 才能保住。 ## 常见错误与排查思路 下面这些坑都是我自己踩过的,按出现频率从高到低排: - 重定向循环(ERR_TOO_MANY_REDIRECTS):原因通常是站点在反向代理 (https://zhangwenbao.com/apache-proxy.html)或负载均衡后面,{HTTPS} 始终为OFF。解决办法是改用 {HTTP_X_FORWARDED_PROTO} 这个头来判断: - 500.19 配置错误:99%是web.config XML格式有问题,少了闭合标签或者多了空格。把文件丢进VSCode用XML插件格式化一下立刻能定位。 - 跳转后CSS、图片404:HTML里写了绝对路径 http://,跳到HTTPS后混合内容被拦截。改成相对协议 // 或者直接用 https:// 即可。 - 后台登录后又跳回HTTP:业务代码里有硬编码的 Response.Redirect 拼 http:// 的字符串,全局搜替换掉。 - 微信内打不开:除了IIS这边的跳转,还要去微信公众平台后台把业务域名也改成HTTPS。 - 查询字符串丢失:URL Rewrite 默认 {R:1} 只捕获 path 不含 query。如果业务依赖查询字符串,要在 action 里加 appendQueryString='true',或者显式写 https://{HTTP_HOST}/{R:1}?{QUERY_STRING}。 - POST 请求被改成 GET:301/302 浏览器会自动把 POST 改成 GET。如果是接口跳转,要用 307/308 状态码(在 URL Rewrite 里把 redirectType 设为 SeeOther 或 Temporary 不够,需要 customRedirectStatusCode 字段)。 排查时我习惯打开IIS的"失败请求跟踪规则",把301也加进追踪范围,能直接看到每一条规则的命中情况,比盯日志快得多。 ## HSTS 与 HSTS Preload List 配置 301 解决了首次跳转,但浏览器每次新进站还是先发一次 HTTP 请求。HSTS(HTTP Strict Transport Security)能让浏览器在 max-age 期内直接跳过 HTTP,省一次 RTT。在 web.config 里加这段: 三个参数: - max-age=31536000 是 1 年。HSTS 推荐至少 6 个月,preload 列表要求至少 1 年。 - includeSubDomains 把所有子域名也纳入。配前提:所有子域都必须支持 HTTPS,否则会把子域全打死。 - preload 表示你愿意把这个域名提交到 Chromium 的 HSTS preload list,提交完后所有 Chrome/Edge/Safari/Firefox 首次访问就直接 HTTPS,连第一次 HTTP 请求都不发。提交地址是 hstspreload.org,提交前要确保上述两项都满足。 注意:HSTS 一旦生效,回滚很难。如果你后来发现某个子域不支持 HTTPS,已经被 includeSubDomains 锁定的浏览器会拒绝访问那个子域,必须等 max-age 到期或者用户手动清缓存。所以上线 HSTS 之前先用短 max-age(比如 300 秒)小流量测试。 ## IIS 不同版本下的配置差异 Windows 版本 | IIS 版本 | HTTP/2 支持 | OCSP Stapling | 注意事项 | Server 2008 R2 | IIS 7.5 | 否 | 需手动开启 | URL Rewrite 必装;TLS 1.2 需 KB 补丁 | Server 2012 R2 | IIS 8.5 | 否 | 默认开启 | TLS 1.3 不支持 | Server 2016 | IIS 10 | 是 | 默认开启 | 支持 ALPN,HTTP/2 自动生效 | Server 2019 | IIS 10 | 是 | 默认开启 | 支持 TLS 1.3 需 21H2 起的累积更新 | Server 2022 | IIS 10 | 是 | 默认开启 | QUIC/HTTP3 需手动启用 | 这张表对应的实战意义:如果你的客户站还在 2008 R2 上跑,先升 TLS 协议层(IIS Crypto 一键脚本可以禁用 SSLv3/TLSv1.0/1.1),再做跳转规则。直接照搬本文 web.config 在所有 IIS 7+ 都能跑,但要享受 HTTP/2 性能加成必须 IIS 10。 ## 和宝塔、Nginx、Apache方案的对比 经常有读者问我:能不能用宝塔面板里的一键HTTPS跳转?或者干脆把IIS换成Nginx?我的看法是: - 宝塔Windows版确实能在IIS站点设置里勾一个"强制HTTPS",本质上就是帮你写了上面那段web.config,原理一致。怕手抖写错配置的话,用宝塔无妨。 - Nginx写起来更短: server { listen 80; server_name example.com; return 301 https://$host$request_uri; } 但Windows上跑Nginx不如Linux稳定,老站点也未必方便迁移。 - Apache用 .htaccess 写法: RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L] 如果你和我一样还在维护一些ASP/.NET老站,IIS + URL Rewrite这套组合稳定性是最高的,不要为了潮流强行换栈。配置写对一次,可以用很多年。 ## 跨方案性能对比 我用 ApacheBench 在同一台 Server 2019 上跑过四组对比(10000 请求、并发 50,目标 / 静态首页 25KB): 方案 | 301 跳转耗时 P95 | 跳转后 HTTPS 响应 P95 | RPS | IIS 10 + URL Rewrite | 1.8 ms | 9.2 ms | 2740 | IIS 10 + 业务层代码跳转 | 4.6 ms | 同上 | 2310 | Nginx 1.24 反向代理转 IIS | 1.2 ms | 14.5 ms | 2110 | 宝塔 Windows 自动配置 | 1.9 ms | 9.4 ms | 2720 | 结论:URL Rewrite 在 IIS 上是最干净的方案,比业务层代码跳转快 2~3 倍——业务跳转要先初始化 .NET pipeline,URL Rewrite 在更早的请求阶段就拦截掉了。 ## 日志分析与监控 跳转上线后建议追踪两类指标: - 仍在产生 HTTP 请求的客户端来源:W3C 日志的 cs-method/cs-uri/cs-uri-query 里检索 sc-status=301 的占比。健康站点上线第一周大约 8~15% 的请求会被跳转,三周内降到 1~3% 是正常的。如果一直居高不下,说明有外部链接或站内残留没更新。 - 跳转失败率:如果出现 500 或 502,定位到 web.config 写错或反向代理 X-Forwarded-Proto 配置丢失。 用 PowerShell 一行扫日志: Import-Csv 'C:\inetpub\logs\LogFiles\W3SVC1\u_ex*.log' -Delimiter ' ' | Where-Object {$_.'sc-status' -eq '301'} | Group-Object 'cs(Referer)' | Sort-Object Count -Descending | Select-Object -First 20 Name, Count 这条命令能告诉你哪些 Referer 还在发 HTTP 请求,是更新内链 (https://zhangwenbao.com/significantlink-relatedlink-schema-internal-linking.html)/外链最直接的依据。 ## 常见问题解答 ## 可以用302临时跳转代替301吗? 技术上完全可以,把redirectType改成Found即可。但搜索引擎不会迁移权重,只把它当作临时变更。如果你的目的是把SEO权重从HTTP转到HTTPS,必须用301。一种特殊情况是站点正在大改版、HTTPS 部分页面尚未完全就绪,可以先 302 临时跳转,全部稳定后改回 301。 ## HSTS还需要单独配置吗? 建议配置。301只能解决用户第一次访问的跳转,HSTS能让浏览器在缓存有效期内绕过HTTP直接走HTTPS。在上面的web.config system.webServer节点下追加 customHeaders 即可。但 HSTS 一旦生效回滚困难,建议先小 max-age 测试再放大到 1 年。 ## 泛域名也能用同一份web.config吗? 可以。{HTTP_HOST}会自动取出当前请求里的域名,无论是www、m、还是其它子域,跳转后会保留同一个二级域名。如果你要做"所有子域统一跳到主域",写法是把 url='https://example.com/{R:1}' 改成绝对主域名即可,但要注意子域用户访问 m.example.com 跳到 example.com 会丢移动版内容。 ## 跳转之后百度排名会下降吗? 短期内可能会有1到2周的小波动,属于正常现象。只要保证301永久跳转、内链全部更新成HTTPS、sitemap (https://zhangwenbao.com/discuz-portal-sitemap.html)也用HTTPS重新提交,权重会顺利平移。我自己几个站点跳完之后,三周左右排名就完全恢复并略有上升。具体加速恢复的做法是登录百度站长平台提交 HTTPS 改造申请,百度会优先安排重新抓取。 ## 跳转之后老的 HTTP URL 还需要保留吗? 需要。301 跳转生效的前提是搜索引擎/用户能访问到那个 HTTP URL 才能被引导到 HTTPS。如果你直接把 80 端口关掉,搜索引擎会把所有 HTTP 索引当作 404 丢掉,权重无法转移。正确做法是 80 端口继续监听、所有请求 301 跳到 443,至少保持半年到 1 年再考虑是否关 80。 ## 反向代理(Nginx/CDN)后面的 IIS 怎么避免重定向死循环? 关键是别再用 {HTTPS} 这个变量,因为 IIS 收到的总是 HTTP(代理已经卸 SSL)。改用代理传过来的 X-Forwarded-Proto 头:conditions 里 add input='{HTTP_X_FORWARDED_PROTO}' pattern='^http$'。如果代理没传这个头,要么在代理上加一行 proxy_set_header X-Forwarded-Proto $scheme; 要么直接在代理层做 80→443 跳转,IIS 这边只保留 443 监听。 ## POST 请求也会被 301 跳转吗?跳转后请求体会丢吗? 会被跳转,但浏览器在跳 301/302 时会自动把 POST 改成 GET,请求体丢失。如果你的接口必须保留 POST 方法,需要用 307 或 308 状态码——这两个明确告诉客户端"换 URL 但保留方法和 body"。URL Rewrite 默认只支持 Permanent(301)/Found(302)/Temporary(307)/SeeOther(303),要 308 需要走自定义模块。但 API 接口跳转的真正解法是改前端调用直接走 HTTPS,不要依赖跳转。 ## 站点有 ws://(WebSocket)连接,跳转会影响吗? 会。WebSocket 走的是 ws:// 或 wss:// 协议,不是 http://。但浏览器只会从 https:// 页面发起 wss://,不会发起 ws://(被 mixed content 拦截)。所以你的 JS 里 new WebSocket('ws://...') 在 HTTPS 页面下必然失败,必须改成 wss://。这跟 301 跳转无直接关系,但是 HTTPS 改造时必须一并处理。 ## 权威参考资料 ## IIS403无权使用凭据查看目录?6步排查修复指南 - URL:https://zhangwenbao.com/the-iis-website-opens-to-indicate-that-you-have-no-right-to-use-the-credentials-provided-to-view-the-solution-to-this-directory.html - 分类:Windows - 发布:2017-02-28 | 更新:2026-06-02 - 摘要:IIS网站报403禁止访问、无权使用凭据查看此目录,原因分好几种。本文按403.14、403.2、403.6、403.8等子状态码分类讲根因,给出添加默认文档、配置IUSR权限、修复处理程序映射、读IIS日志的sc-win32-status等排查流程,覆盖Windows Server 2008到IIS 10。 - 关键词:目录权限,IIS,Windows Server,ASP,默认文档 > **TLDR**:摘要:IIS网站报403禁止访问、无权使用凭据查看此目录,原因分好几种,第一步得先确认子状态码。本文按403.14、403.2、403.6、403.8等子码分类讲根因,给出添加默认文档、检查匿名身份验证与文件权限、修复处理程序映射、读IIS日志的sc-win32-status、排查IP限制与URL重写与hosts的完整流程,覆盖Windows Server 2008到IIS 10。 > 摘要:IIS网站报403禁止访问、无权使用凭据查看此目录,原因分好几种,第一步得先确认子状态码。本文按403.14、403.2、403.6、403.8等子码分类讲根因,给出添加默认文档、检查匿名身份验证与文件权限、修复处理程序映射、读IIS日志的sc-win32-status、排查IP限制与URL重写与hosts的完整流程,覆盖Windows Server 2008到IIS 10。 保哥这条笔记最早是 2017 年从息壤搬站到阿里云的时候记录的。那一年我买了一台 Windows Server 2008 的 ECS 实例,把两个 ASP 站点搬上去,结果一打开就是熟悉的 403 红字提示:“访问被拒绝,您无权使用所提供的凭据查看此目录或页面。” 这个错误信息看上去像是权限问题,让人第一反应去查文件系统权限、应用程序池身份、用户账户密码,结果折腾半天才发现根本是另一回事。这些年我陆陆续续帮朋友处理过类似情况,从老版本的 IIS 6 到现代的 IIS 10 都遇到过,触发原因不止一种。今天把这些场景一次性整理出来,按概率从高到低排序,方便你快速定位并修复。读完这篇文章,你大概能省下两到三个小时的搜索和试错时间。 ## 第一时间确认错误的子状态码到底是什么 微软的 IIS 在返回 403 错误时其实有十几个细分子代码,单看“您无权使用所提供的凭据”这句话不够准确。先在浏览器地址栏访问网站,把页面拉到最底部或者按 F12 看网络面板,找到具体的 HTTP 状态码。 常见的 403 子代码含义如下: 403.14 “Web 服务器配置为不列出此目录的内容”。这是出现频率最高的,本质是默认文档没设对。 403.1 “执行访问被拒绝”。一般是脚本权限问题,应用程序池没启用对应的运行时(比如 ASP 或 PHP 没装)。 403.2 “读取访问被拒绝”。物理目录的文件系统权限不够,匿名用户读不到文件。 403.4 “要求安全连接”。明文请求被强制跳到加密通道但证书没装好。 403.6 “客户端 IP 被拒绝”。IIS 的 IP 限制规则把客户端拦了。 403.7 “要求客户端证书”。常见于政府、银行、医院的内部系统。 403.8 “站点访问被拒绝”。一般是 hosts 文件和站点绑定不一致。 403.16 “客户端证书不受信任或无效”。客户端证书过期或来自不受信任的 CA。 403.18 “在当前应用程序池中不能执行所请求的 URL”。多站点共享应用程序池时的常见错误。 403.19 “不能为这个应用程序池中的客户端执行 CGI”。应用程序池标识权限不够。 保哥当年遇到的就是 403.14,原因是从息壤迁过来的站点入口文件叫小写的 index.asp,但新装的 IIS 默认文档列表里只有几个常见名字,唯独没有这个小写的入口文件。浏览器请求根路径,IIS 找不到默认文档,又因为目录浏览功能默认关闭,就直接抛 403 了。 搞清楚子状态码这一步特别关键,可以让你少走至少一半的弯路。如果你看到的子状态码是 403.14,直接跳到下面的步骤一;如果是 403.2,跳到步骤二;如果是 403.6 或 403.8,跳到步骤五;如果是 403.1,跳到步骤三的处理程序映射部分。 ## 排查步骤一:检查并添加默认文档 这是出现频率最高的原因,先从这里下手。 第一步,在服务器上打开 IIS 管理器。可以在“运行”框里输入inetmgr命令直接打开,或者从“开始 → 管理工具”里找。 第二步,在左侧导航树里展开“网站”节点,点选你的目标站点。 第三步,在右侧功能视图里双击“默认文档”图标。 第四步,看列表里有没有你的入口文件名。常见的入口文件包括小写的index.asp、index.html、index.htm、index.php,也包括大写开头的Default.asp、Default.aspx。注意 Windows 文件系统不区分大小写,但 IIS 的默认文档列表会按列表顺序匹配,第一个能找到对应文件的会被使用。 第五步,如果列表里没有你的入口文件,点右侧的“添加”按钮,输入入口文件名比如index.asp,确定。 第六步,添加后默认排在列表最下面,需要选中它,点右侧的“上移”按钮,挪到列表最上方,让 IIS 优先匹配你的入口文件。 这一步做完,刷新浏览器,绝大多数 403.14 错误都能解决。 如果想用配置文件批量处理,也可以直接编辑站点目录下的 web.config (https://zhangwenbao.com/web-config-asp-net-multiple-domain-redirect.html) 文件: 保哥习惯把 clear 标签写在最前面,这样可以清空继承自父配置的默认文档列表,完全按当前站点的需求来排序,避免出现“我已经把入口文件放最上面了,但 IIS 还是先找别的”这种诡异情况。这个写法在 IIS 7 及以上版本都有效。 ## 排查步骤二:检查匿名身份验证和文件系统权限 如果默认文档已经设好了还是 403,第二个怀疑对象是身份验证配置。子状态码通常是 403.2。 打开 IIS 管理器,选中站点,双击“身份验证”图标,确认两件事。 第一,匿名身份验证必须是“已启用”状态。如果显示“已禁用”,右键启用。绝大部分公开访问的网站都需要启用匿名访问。 第二,匿名身份验证使用的用户。右键“编辑”,确认使用的是 IUSR 或者“应用程序池标识”。一般推荐选“应用程序池标识”,权限管理更现代,每个站点独立一个身份,相互之间不会越权访问。 然后回到服务器文件系统,找到站点的物理目录,比如典型路径下的网站文件夹,右键属性,安全选项卡,确认以下三个用户或组至少有“读取和执行”权限: IIS_IUSRS 用户组(IIS 工作进程所在的组);IUSR 用户(经典匿名用户);应用程序池标识(格式是IIS AppPool\加上你的应用池名)。 保哥踩过一个坑:从其他服务器复制过来的网站文件夹,文件系统权限是空的,只有系统账户和管理员组能访问。当时反复检查 IIS 配置都没问题,结果是文件夹权限没继承。解决办法是右键文件夹,属性,安全,高级,启用继承,应用到所有子对象。这一步在迁移服务器时几乎是必须做的,建议写到部署清单里。 除了 GUI 操作,也可以用命令行批量修复权限。在管理员 PowerShell 里执行: $path = "C:\inetpub\wwwroot\mysite" icacls $path /grant "IIS_IUSRS:(OI)(CI)RX" /T icacls $path /grant "IUSR:(OI)(CI)RX" /T icacls $path /grant "IIS AppPool\DefaultAppPool:(OI)(CI)RX" /T 这段命令会递归地给三个核心账号添加读取和执行权限。(OI)(CI)表示对象继承和容器继承,RX是读取加执行权限,/T是递归处理所有子目录和文件。 ## 排查步骤三:检查目录浏览和处理程序映射 如果你的站点根目录确实没有放任何默认文档,比如纯下载站、API 接口站、静态资源服务器,那就需要明确启用“目录浏览”功能:打开 IIS 管理器选中目标站点,双击“目录浏览”图标,右侧操作面板点“启用”。 但要注意,目录浏览启用后整个目录的文件结构都会暴露在前台,安全性较差,生产环境一般不建议这么做。更好的方案是写一个空的占位首页文件,让用户访问根路径时看到一个友好的提示页或重定向。 另一个容易被忽略的点是处理程序映射。新装的 IIS 如果没有勾选 ASP 或 ASP.NET 角色服务,就算放了入口文件也无法解析,会回退到目录浏览,进而报 403 错误。修复步骤: 第一,打开服务器管理器,添加角色和功能。 第二,找到“Web 服务器(IIS) → 应用程序开发”分支。 第三,勾选 ASP、ASP.NET 各版本、ISAPI 扩展、ISAPI 筛选器。如果是 ASP 站点,还要额外勾选 ASP 模块。 第四,安装完成后重启 IIS 服务,可以用以下命令一键完成: iisreset /restart 这个命令会停止再启动所有 IIS 相关服务,整个过程通常只需要几秒钟。如果服务器上有多个站点都在运行,重启会让所有站点短暂不可用,最好选在凌晨低峰时段做。 如果你的站点是 PHP 站点,还需要确认 PHP 处理器已经映射到.php扩展名。在“处理程序映射”界面看有没有一行 FastCGI 模块指向.php且模块路径是 PHP-CGI.exe。如果没有,需要手动添加。 ## 排查步骤四:日志才是最终答案 上面三步覆盖了 95% 的场景,剩下 5% 的疑难杂症必须靠日志。IIS 默认日志路径在系统盘的 inetpub 目录下的 logs 子目录里。 C:\inetpub\logs\LogFiles\W3SVC{站点ID}\ 站点编号可以在 IIS 管理器里选中站点后,右下角的“高级设置”里看到。日志文件按日期命名,类似u_ex251201.log这种格式(u_ex 加 yymmdd)。用记事本或者更专业的工具打开最新一个,搜索 403 关键字,找到刚才出错的那一行,重点看末尾几个字段: sc-status sc-substatus sc-win32-status 403 14 0 sc-status 是 HTTP 主状态码,sc-substatus 是子状态码(前面提到的 403.14 就是这里来的),sc-win32-status 是 Windows 错误代码(零表示无附加错误,非零数字代表特定的系统错误)。 如果 Windows 错误代码不是零,可以用命令查含义: net helpmsg 5 保哥之前遇到过 Windows 错误代码是 5 的情况,命令显示“拒绝访问”,这就明确指向文件系统权限问题,跟 IIS 配置无关。这种思路比盲目改配置高效得多,能直接锁定问题根源。读日志的能力是运维水平拉开差距的关键,强烈建议多花时间练习。 除了 IIS 自身的日志,Windows 系统事件日志也是排查 403 的重要信息源。在“事件查看器 → Windows 日志 → 应用程序”里搜索 ASP.NET 或 W3SVC 相关的错误事件,能看到应用层抛出的详细异常堆栈,有时候比 IIS 日志更具体。 ## 排查步骤五:IP 限制、URL 重写、hosts 配置问题 如果子状态码是 403.6 或 403.8,问题在网络访问层面。这种情况下默认文档和文件权限都不是问题,需要换思路。 403.6 IP 限制:检查 IIS 的“IP 地址和域限制”模块,看有没有黑白名单规则把你的客户端 IP 拦截了。常见场景是:从客户机访问正常但从办公室访问 403,因为办公室 IP 段被加到了黑名单。或者反过来:白名单只放了几个固定 IP,所有其他 IP 都被拒绝。 403.8 站点访问被拒绝:检查站点绑定是否和访问 URL 匹配。比如绑定的是example.com:80,但用户访问的是www.example.com:80,IIS 会拒绝。需要在“绑定”里同时加上带 www 和不带 www 的绑定。或者使用 URL 重写规则做 301 跳转统一域名。 另一个常见的 403.8 来源是 hosts 文件和站点绑定不一致。如果服务器自己的 hosts 文件指定example.com解析到127.0.0.1,而站点绑定是192.168.1.100,本机访问会失败。检查 C:\Windows\System32\drivers\etc\hosts 文件,确认本地映射正确。 ## 排查步骤六:现代化迁移建议与运维总结 2017 年那会儿用 Windows Server 2008 加 IIS 7.5 加 ASP 还能凑合,2026 年再这么搞就是给自己挖坑。微软已经停止 Windows Server 2008 的扩展支持,安全更新也基本没有了。如果你现在还在用类似环境,保哥的建议是分四步走。 第一,ASP 站点尽快迁移到现代后端框架。经典 ASP 用的脚本语言已经超过 20 年没更新过,安全漏洞多,开发效率也低。可以考虑迁移到现代.NET 框架(ASP.NET Core)或者 PHP 8。.NET 8 的 LTS 版本会一直支持到 2026 年 11 月,是安全可控的长期选择。 第二,服务器系统升级到 Windows Server 2019 或 2022,或者直接转 Linux。新版 IIS 10 默认配置就比 7.5 好很多,很多权限坑直接消失。Linux 加 Nginx 加 PHP 的组合维护成本更低,且开源社区更活跃。 第三,静态站点考虑搬到内容分发网络加对象存储。比如阿里云对象存储 OSS 加 CDN 组合,比维护一台 IIS 服务器省心十倍,还便宜。完全静态的网站月费可能只有几块钱。 第四,必须保留经典 ASP 的话,至少把站点放到反向代理 (https://zhangwenbao.com/nginx-proxy.html)后面。前面用 Nginx 做安全连接卸载和 Web 应用防火墙,IIS 只跑业务逻辑,安全性提升一个量级。 保哥这些年的总结是,运维问题大部分时候都不是单点故障,而是配置组合的问题。今天这台服务器没事,明天换个客户端浏览器或者换个网络环境就可能复现。建立完整的部署清单和巡检脚本,比临时救火重要得多。 给一份保哥常用的部署清单作为参考:新站点上线时检查的 12 项是——默认文档配置正确、应用程序池标识权限正确、文件系统权限继承启用、防火墙端口放行、安全组规则配置、ISP 80 端口是否封禁、SSL 证书安装且未过期、HTTP 到 HTTPS 跳转配置、www 和非 www 域名统一处理、自定义错误页面配置、Server 头信息隐藏、IIS 日志开启且定期归档。每一项都过一遍能避免绝大部分上线后的 403 和其他错误。这份清单可以做成 Markdown 模板贴到团队 Wiki 里,每次部署照着勾选。 ## 常见问题解答 ## 默认文档加了,重启 IIS 也还是 403 怎么办 按顺序检查:第一,入口文件名拼写是否完全一致,虽然 Windows 不区分大小写但建议保持和实际文件名一致;第二,入口文件是否真的存在于站点根目录,是不是被部署脚本漏掉了或者文件名带了隐藏的.txt扩展(在文件夹选项里勾选“显示已知扩展名”确认);第三,应用程序池是否启动状态,可以在 IIS 管理器里看应用池列表;第四,应用程序池的.NET CLR 版本和托管管道模式是否匹配你的代码,经典 ASP 应该选“无托管代码”。 ## 浏览器一直显示 403,但其他人访问正常 大概率是 IIS 的 IP 限制规则、URL 重写规则或者你本地的主机映射文件出了问题。先用手机的移动数据网络访问试试是不是稳定,再看 IIS 的“IP 地址和域限制”里有没有黑白名单。也可以临时清空浏览器缓存和 Cookie 试试。如果还是不行,用tracert example.com看网络链路有没有问题,可能是中间路由器拦截了。 ## 本地能访问外网访问就 403 怎么办 检查站点绑定。IIS 管理器里选中站点,右侧“绑定”,看主机名和端口是不是配对。然后检查防火墙,Windows 防火墙的“入站规则”要放行 80 和 443 端口。云服务器还要在控制台的安全组规则里开放对应端口,公网能不能进来要看安全组的最外层规则。最后还要看一下 ISP 是否封禁了 80 端口(国内宽带运营商有时候会封 80 端口要求备案,需要换成 8080 或者完成 ICP 备案)。 ## 升级到 IIS 10 之后还会不会遇到这种 403 会,但概率显著降低。IIS 10 默认启用了更多文档名,权限继承也更智能。最常见的剩余坑是.NET Core 的进程内部署模式下应用没启动起来,这时候 403 信息会变成 502.5,跟旧版的 403.14 完全不一样。看到 502.5 直接去查应用日志,多半是新版运行时没装或者版本不对。 ## 从经典 ASP 迁移到现代框架时还需要保留这套排查思路吗 部分需要保留。默认文档、文件系统权限、应用程序池身份这些概念在新一代框架里依然存在,只是配置方式略有不同。日志读取的能力更是通用技能,不管什么后端框架都用得上。保哥的建议是把这套排查思路抽象成一份运维清单,每次部署新站点都过一遍,能避免百分之八九十的低级错误。 ## 能不能写个脚本自动检测这些 403 问题 可以。保哥自己写过一个 PowerShell 巡检脚本,遍历所有站点,检查默认文档列表是否包含期望的入口文件、应用程序池是否处于运行状态、物理目录是否存在并且有正确的权限。如果发现异常会写到事件日志里,再配合监控平台告警。这种方法比临时救火主动得多,特别适合管理多个站点的团队。脚本核心逻辑大约 100 行,建议每天凌晨定时跑一次。 ## 403 错误页面能不能自定义 能。在 IIS 管理器里选中站点,双击“错误页”,找到 403 那一行右键“编辑”,选“在此站点上执行 URL”并填入你的自定义页面路径。这样所有 403 错误都会显示你设计的友好页面,而不是 IIS 默认的红字英文页面。但注意不要在自定义页面里暴露太多系统信息(比如服务器路径、IIS 版本号),这些信息对攻击者有帮助。 ## 如何完全禁止 IIS 暴露版本信息 有两层:第一层是 HTTP 响应头 Server 字段,可以在 web.config 里加移除。第二层是 X-Powered-By 字段,在 web.config 里加。这两个操作组合可以让 IIS 在外部看起来像一个完全匿名的 Web 服务器,增加攻击者的侦察成本。 ## IIS10装不上URL Rewrite2.0?注册表临时改MajorVersion实战修复(含PowerShell脚本与3种方案) - URL:https://zhangwenbao.com/the-url-rewrite-tool-cannot-be-installed-on-the-iis100-web-platform-installation-tool.html - 分类:Windows - 发布:2017-01-22 | 更新:2026-06-02 - 摘要:IIS 10通过Web Platform Installer装URL Rewrite总是中断,根因不是真不兼容,而是.msi声明的版本上限只到9.x,碰上MajorVersion=10就被判违例。本文从WebPI日志拆解检测流程,给出改注册表、PowerShell脚本、绕过WebPI直装离线msi三套方案。 - 关键词:rewrite,IIS,Windows Server,PowerShell,Web平台安装程序 > **TLDR**:摘要:IIS 10通过Web Platform Installer装URL Rewrite总是中断,根因不是真不兼容,而是.msi声明的版本上限只到9.x,碰上MajorVersion等于10就被判违例。本文从WebPI日志拆解检测流程,给出三套方案——手改注册表MajorVersion、PowerShell一键脚本、跳过WebPI直装官方离线msi,再附四种安装后验证、实测耗时基准和IIS 10与11在URL Rewrite行为上的差异。 > 摘要:IIS 10通过Web Platform Installer装URL Rewrite总是中断,根因不是真不兼容,而是.msi声明的版本上限只到9.x,碰上MajorVersion等于10就被判违例。本文从WebPI日志拆解检测流程,给出三套方案——手改注册表MajorVersion、PowerShell一键脚本、跳过WebPI直装官方离线msi,再附四种安装后验证、实测耗时基准和IIS 10与11在URL Rewrite行为上的差异。 用 Windows 自带的 Web 平台安装程序(Web Platform Installer,简称 WebPI)给 IIS 10 装 URL Rewrite Module 2.0 时,安装到一半弹出红字警告: > 很遗憾,无法安装下列产品 URL Rewrite Module 2.0 同样的 .msi 在 IIS 7 / IIS 8 上没问题,唯独 IIS 10 装不上。这个坑从 Windows 10 + IIS 10 时代起一直延续到 Windows Server 2025(IIS 11),底层逻辑没改过。这一篇把根因、3 种可落地的修复方案、WebPI 内部日志诊断、缓存中毒清理、AppPool 32-bit 隐藏陷阱、IIS 10 与 IIS 11 在 URL Rewrite 行为上的具体差异,全部用我自己装过 30+ 次的实测数据写清楚。 ## 报错原因:注册表 MajorVersion 与 .msi 元数据不匹配 WebPI 安装一个组件前会读注册表里 IIS 当前的主版本号,位置如下: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\InetStp └─ MajorVersion (REG_DWORD) └─ MinorVersion (REG_DWORD) └─ VersionString (REG_SZ) └─ InstallPath (REG_SZ) IIS 10 的 MajorVersion = 10(十六进制 0xA)。但微软官方 URL Rewrite 2.0 / 2.1 的 .msi 元数据(每个 .msi 内嵌的 Manifest.xml 中的 installerArguments 段与 SupportedVersions 段)写死的支持版本上限只到 9.x。WebPI 检测到 10 > 9 就直接判"产品不兼容",根本不会调起 .msi 实际安装流程。 这一段判断在 %ProgramFiles%\Microsoft\Web Platform Installer\WebPlatformInstaller.exe 主程序内部,由 Microsoft.Web.PlatformInstaller.dll 中的 Product.IsCompatibleWithCurrentEnvironment() 方法实现,用反射也能在 ILSpy 里直接看到——它对 IIS 版本走的是 严格大于即不兼容 的判定,而不是 大于等于历史最低支持版本即可,这是这个坑的设计层根因。 ## 怎么用 WebPI 内部日志确认是不是这个错 WebPI 失败时弹的红字几乎不带具体信息。真正的诊断要看其内部日志,路径是: %LOCALAPPDATA%\Microsoft\Web Platform Installer\logs\install\ └── <时间戳>_UrlRewrite2.txt 这个目录默认按"产品 × 时间戳"分文件,每次失败都新建一个。打开最新那份找以下字符串可以快速分诊: - FAILED INCOMPATIBLE 或 Product is not compatible——本文这个版本检测错。 - FAILED CRC——下载文件被截断,跟版本无关,重下即可。 - HRESULT: 0x80070643——Windows Installer 自身错误,多半是 .NET Framework 缺失(参见踩坑 6.7)。 - The system cannot find the file specified——WebPI 缓存损坏,需清缓存(参见踩坑 6.5)。 把这一段日志文件路径记住——后面所有方法的判断都会回到日志看。 ## 微软为什么不修这个 .msi? 微软在 2022 年 7 月正式宣布 Web 平台安装程序停止服务,原因是其依赖的元数据下载源 https://www.microsoft.com/web/webpi/5.0/WebProductList.xml 和组件 CDN (https://zhangwenbao.com/cdn-edge-caching-strategy-ttl-cache-control-purge-origin-shield.html) 已下线(IIS 团队博客的官方公告里写明的)。在那之前 URL Rewrite 包就停止主版本维护了——2.1 是最后一个稳定版,元数据版本上限没人去更新。这就是说: - WebPI 本身在新装 Windows 11 22H2+ 上已无法启动(连不上元数据服务器); - 但 URL Rewrite 模块本身仍是 IIS 现代化部署的核心,微软会在 IIS 后续版本继续维护; - 所以"装不上"的责任端在 WebPI 已 EOL,不在 URL Rewrite。 ## 注册表 MajorVersion 是怎么写进去的 这个值在 IIS 安装阶段由系统组件 %windir%\system32\inetsrv\InetMgr.exe 安装包写入,不会随 IIS 配置变化。手改它只是欺骗 WebPI 一次,不影响 IIS 任何运行时行为——HTTP.SYS、worker process、应用程序池、各模块加载链路全部读其它内部接口(iiscore.dll 中的 GetServerVersion()),不依赖这个注册表项。所以放心改。 ## 修复方法 1:手改注册表 MajorVersion(最常用) 这是网上最早出现的修法,30 秒搞定,适合"装一次就走"的场景。 ## 操作步骤 - 按 Win + R,输入 regedit,回车打开注册表编辑器(需要管理员权限)。 - 定位到:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\InetStp - 右侧找到 MajorVersion,双击改值(默认是十进制 10),改成 9,确定。 - 回到 WebPI 重新安装 URL Rewrite Module 2.0,这次就能装下去了。 - 安装完成后,把 MajorVersion 改回 10。 - 用管理员命令行执行 iisreset 重启 IIS 服务。 ## 三个最容易看走眼的细节 - 双击 MajorVersion 时,注册表编辑器默认显示十六进制(0xA = 10),改成 9 时也要在十六进制下输入 9,或先切换到十进制再改。如果切换到二进制视图改,会改错相邻字节。 - WebPI 一定要 关闭再打开 才会重新读注册表——不重启它会延用第一次启动时缓存的版本号。这一点是社区里最普遍漏掉的步骤,"我改了为什么还是不行"的发问大半都是这个原因。 - 装完后第一时间回去看 MajorVersion 是不是已经被改回去(很多笔记忘了这步),不然下次装其它依赖正确 IIS 版本的组件(ARR、Web Deploy、Application Initialization)会再次踩坑。 ## 手改方式不要做的事 - 不要把 MajorVersion 直接删掉。WebPI 读不到该键时会抛 NullReferenceException,连安装界面都进不去。 - 不要改 MinorVersion。MinorVersion 在 IIS 10 是 0、IIS 7.5 是 5、IIS 8.5 是 5。WebPI 检测时只读 MajorVersion,改 MinorVersion 是无效操作但可能让监控工具报警。 - 不要改 VersionString。这是字符串"10.0",WebPI 内部不读它,但事件查看器和 IIS 管理器界面读,改了之后界面会显示错版本,吓自己。 ## 修复方法 2:PowerShell 一键脚本 批量给多台服务器装、或写到自动化部署里时,用脚本最稳。下面这段以管理员身份运行 PowerShell 即可: $regPath = 'HKLM:\SOFTWARE\Microsoft\InetStp' $origVer = (Get-ItemProperty -Path $regPath -Name 'MajorVersion').MajorVersion Write-Host "原 MajorVersion = $origVer" # 1. 临时改成比真实版本小 1 的值 $lowered = [Math]::Max($origVer - 1, 7) # 兜底不低于 7 Set-ItemProperty -Path $regPath -Name 'MajorVersion' -Value $lowered # 2. 调起 WebPI 命令行安装 & "$env:ProgramFiles\Microsoft\Web Platform Installer\WebpiCmd.exe" /Install /Products:UrlRewrite2 /AcceptEula # 3. 改回原值(动态读,不写死) Set-ItemProperty -Path $regPath -Name 'MajorVersion' -Value $origVer # 4. 重启 IIS & iisreset 这个脚本和网上常见版本的区别在于两点:① 动态读 origVer 再写回,而不是写死 10——Server 2025 (IIS 11) 上 MajorVersion = 11,写死 10 反而搞错;② 降版本时降到 origVer - 1 而不是写死 9——避免一刀切对未来更高版本不适用。 ## 升级版:try/finally 兜底版本号回写 正式自动化部署的话,建议给上面再套一层 try/catch + 日志,避免脚本中途出错时注册表卡在低版本: $regPath = 'HKLM:\SOFTWARE\Microsoft\InetStp' $logPath = "$env:TEMP\install-urlrewrite-$(Get-Date -Format 'yyyyMMdd-HHmmss').log" Start-Transcript -Path $logPath -Force $origVer = (Get-ItemProperty -Path $regPath -Name 'MajorVersion').MajorVersion $lowered = [Math]::Max($origVer - 1, 7) try { Write-Host "[INFO] 原 MajorVersion = $origVer,临时降为 $lowered" Set-ItemProperty -Path $regPath -Name 'MajorVersion' -Value $lowered $webpi = "$env:ProgramFiles\Microsoft\Web Platform Installer\WebpiCmd.exe" if (-not (Test-Path $webpi)) { throw "WebPI 未安装:$webpi" } $exit = (Start-Process -FilePath $webpi -ArgumentList '/Install','/Products:UrlRewrite2','/AcceptEula' -Wait -PassThru -NoNewWindow).ExitCode if ($exit -ne 0) { throw "WebPI 退出码 $exit" } Write-Host "[OK] URL Rewrite 安装完成" } catch { Write-Host "[ERROR] $_" -ForegroundColor Red } finally { # 无论成功失败都把版本号写回去 Set-ItemProperty -Path $regPath -Name 'MajorVersion' -Value $origVer Write-Host "[INFO] MajorVersion 恢复为 $origVer" & iisreset Stop-Transcript } 关键是 finally 块——无论 try 里出什么状况,都会把 MajorVersion 写回原值,注册表绝不会被卡住。日志写到 %TEMP%,事后审计很方便。 ## 用 Ansible 部署的写法 如果整套基础设施走配置管理工具,Ansible 的写法(win_regedit + win_command): - name: Read current IIS MajorVersion win_reg_stat: path: HKLM:\SOFTWARE\Microsoft\InetStp name: MajorVersion register: iis_ver - name: Lower MajorVersion temporarily win_regedit: path: HKLM:\SOFTWARE\Microsoft\InetStp name: MajorVersion data: "{{ iis_ver.value | int - 1 }}" type: dword - name: Install URL Rewrite via WebPI win_command: > "C:\Program Files\Microsoft\Web Platform Installer\WebpiCmd.exe" /Install /Products:UrlRewrite2 /AcceptEula register: webpi_result - name: Restore IIS MajorVersion (always run) win_regedit: path: HKLM:\SOFTWARE\Microsoft\InetStp name: MajorVersion data: "{{ iis_ver.value }}" type: dword when: iis_ver.value is defined - name: Restart IIS win_service: name: W3SVC state: restarted ## 修复方法 3:跳过 WebPI 直接装官方离线 .msi(推荐) 实际上更干净的做法是 不走 WebPI,直接拿 URL Rewrite 2.x 的离线 .msi 安装包: - x64 系统:下载 rewrite_amd64_zh-CN.msi(或对应英文版 rewrite_amd64_en-US.msi) - x86 系统:下载 rewrite_x86_zh-CN.msi(基本上不会再用了,但 32 位老系统迁移时偶尔需要) 双击安装,不会读注册表的兼容性元数据,整个 WebPI 版本检测逻辑被绕开,自然也就没有"很遗憾,无法安装"的提示。 ## WebPI 退役后 .msi 还能从哪里拿? 这是 2022 年之后大家最头疼的问题——微软下载中心搜 "URL Rewrite" 跳到的页面会指向已停服的 WebPI 安装链路,部分镜像页 404。我用过的还能下到 .msi 的渠道有几个: - IIS.NET 模块目录:https://www.iis.net/downloads/microsoft/url-rewrite 页面下方"Direct Download Links"区——这个页面归 IIS 团队维护,至今(2026 年)仍提供 amd64 / x86 / arm64 三种 .msi 直链。 - Microsoft Download Center 镜像:https://download.microsoft.com/download/... 形式的直链,部分 .msi GUID 存活。Wayback Machine 上有 2018-2020 年的快照能反查到 GUID,再到当前 download.microsoft.com 验证还在。 - Chocolatey:choco install urlrewrite 一行命令解决。Chocolatey 包维护方自己存了 .msi 的镜像,比微软自家更稳定。 - NuGet 包形式:少数企业内网用 NuGet 私有源分发,包名 Microsoft.Web.UrlRewriteModule。 建议公司内部 SCCM / 内网 fileserver 上自存一份 amd64 .msi,避免再依赖外部链路。 ## 静默安装命令(适合脚本部署) 离线 .msi 也支持静默安装,写到批处理或 PowerShell 里: msiexec /i rewrite_amd64_zh-CN.msi /quiet /norestart /log "C:\install-rewrite.log" /quiet 全静默不弹窗、/norestart 装完不强制重启系统、/log 把详细安装日志写到指定路径。日志文件大约 80-200KB,事后排查很方便。 ## .msi 文件指纹(验证下载未被篡改) 从第三方镜像(含 Chocolatey、私有 NuGet、内网 fileserver)下载的 .msi 一定要先校验,避免被植入木马。校验流程: - 到 IIS.NET 模块页(https://www.iis.net/downloads/microsoft/url-rewrite)找 "Direct Download Links" 区,把官方公布的 .msi 文件大小记下来; - 对自己手上的文件跑 PowerShell: $file = '.\rewrite_amd64_zh-CN.msi' "大小: {0:N0} 字节" -f (Get-Item $file).Length "SHA-256: " + (Get-FileHash -Algorithm SHA256 -Path $file).Hash - 把输出的字节数与 IIS.NET 页面公布的一致就行;SHA-256 微软官方不一定公布,但同一份 .msi 在不同来源跑出来的 SHA-256 应该完全相同——可以下两个不同来源(比如 Chocolatey 和 IIS.NET)做交叉对比,两者哈希一致就基本确认未被篡改。 对不上字节数的别装;只对 SHA-256 不一致的优先怀疑下载源被污染,换源重下。 ## 卸载/换版本 用同一个 .msi 卸载: msiexec /x rewrite_amd64_zh-CN.msi /quiet 升级到新版(比如 2.0 → 2.1)直接装新包即可,会自动卸载旧版再装。 ## 安装后验证:四种确认方式 装完之后用以下任一方式确认 URL Rewrite 真的生效了。生产环境建议四种都做一遍。 ## IIS 管理器界面 打开 IIS 管理器(inetmgr),点击站点 → 在中间面板的"功能视图"里找到 URL 重写 图标,能打开就是装成功。能列出"入站规则 / 出站规则"两个选项卡说明模块加载完整。 ## 命令行查模块列表(最靠谱) %windir%\system32\inetsrv\appcmd.exe list modules /name:RewriteModule 装成功的输出: MODULE "RewriteModule" ( type:RewriteModule, preCondition: ) 装失败但 .msi 装"成功"了的输出(比如 64/32 位错装的情形): (空——什么都不输出) 装失败 + .msi 报错的输出: (模块未注册到 IIS,appcmd 报:未发现匹配的模块) 这三种状态的差异在网上极少有人列清,但调试时非常有用——空输出意味着 .dll 文件存在但模块没注册,需要重装;明确报错意味着 .dll 都没写进去,要排查权限或杀软拦截。 ## 物理文件校验 装成功后会有这些文件落地: %windir%\System32\inetsrv\rewrite.dll - 32-bit %windir%\System32\inetsrv\rewrite_x64.dll - 64-bit(实际名字是 rewrite.dll,64 位也是这名) %windir%\System32\inetsrv\config\schema\rewrite_schema.xml %windir%\inetsrv\Logs\* (运行时日志) 关键是 schema 文件——只要 rewrite_schema.xml 在,IIS 才会接受 Web.config (https://zhangwenbao.com/web-config-asp-net-multiple-domain-redirect.html) 里的 段。schema 不在但 .dll 在 = 安装是"半完成"状态,需要重装。 ## PowerShell 一键检测脚本 function Test-UrlRewrite { $issues = @() # 1. 检查 dll 文件 $dll = "$env:windir\System32\inetsrv\rewrite.dll" if (-not (Test-Path $dll)) { $issues += "rewrite.dll 缺失" } # 2. 检查 schema $schema = "$env:windir\System32\inetsrv\config\schema\rewrite_schema.xml" if (-not (Test-Path $schema)) { $issues += "rewrite_schema.xml 缺失" } # 3. 用 appcmd 查注册 $appcmd = "$env:windir\system32\inetsrv\appcmd.exe" $output = & $appcmd list modules /name:RewriteModule 2>$null if ($output -notmatch 'RewriteModule') { $issues += "模块未注册到 IIS" } # 4. 检查注册表 product key $prodKey = 'HKLM:\SOFTWARE\Microsoft\IIS Extensions\URL Rewrite' if (-not (Test-Path $prodKey)) { $issues += "Product 注册表项缺失" } if ($issues.Count -eq 0) { Write-Host "[OK] URL Rewrite 已就绪" -ForegroundColor Green return $true } else { Write-Host "[FAIL] 检测到 $($issues.Count) 个问题:" -ForegroundColor Red $issues | ForEach-Object { Write-Host " - $_" -ForegroundColor Yellow } return $false } } Test-UrlRewrite ## 生产环境踩坑(实战汇总) ## 改完忘记把 MajorVersion 改回去 这是最常见的坑——脚本/手动改了 9 装完,没改回 10,半年后装其它 IIS 组件(ARR、Web Deploy、Microsoft.Net Compatibility 包)又踩同款问题,查半天找不到原因。建议直接走方法 3 的离线 .msi,或方法 2 的 try/finally 自动恢复脚本。 如果发现注册表已经被卡在 9,直接 regedit 改回去即可,不用重装系统。改回 10 后跑一次 iisreset 让其它 service 重新读到正确版本号。 ## 6.2 64 位系统装了 32 位包 x64 服务器一定下 amd64 版的 .msi。装错的症状很有特征——三件套: - 安装过程显示成功; - 但 IIS 管理器里看不到 URL 重写图标; - appcmd 查不到 RewriteModule(输出为空,参见 5.2); - Web.config 里写 段会报 500.19 配置错误。 解决:用 msiexec /x rewrite_x86_zh-CN.msi /quiet 卸载 32 位版,再装 amd64 版。 ## AppPool 启用了 32-bit 模式但 URL Rewrite 装的是 64 位(隐藏陷阱) 这个坑文档里几乎找不到——某个应用程序池在 IIS 管理器里把"启用 32 位应用程序"打勾,但 URL Rewrite 装的是 64 位。症状: - 整站其它 AppPool 正常,唯独这个 AppPool 下的站点 URL Rewrite 规则不生效; - 不报错,规则就像不存在一样静默忽略; - appcmd 查模块状态显示已注册(因为查的是 IIS 全局,不是 AppPool 级); - 事件查看器 "Application" 日志里也没记录。 原因是 32 位 AppPool 加载的是 32 位 IIS worker process(w3wp.exe *32),它去找 %windir%\SysWOW64\inetsrv\rewrite.dll 而不是 %windir%\System32\inetsrv\rewrite.dll,找不到就静默跳过模块。 解决:补装 32 位包。即在 64 位系统上同时装 amd64 + x86 两个 .msi,让两边目录都有文件。或者把 AppPool 的 "启用 32 位应用程序" 关掉切到 64 位。 ## iisreset 还是没生效 少数情况下是 W3SVC 没真停。手动执行: net stop was /y && net start w3svc 或者重启服务器一次。Application Pool 的 preload 模式(启动模式:始终运行)也可能让旧进程不释放,重启服务器最稳。 ## WebPI 缓存中毒(重复尝试后即使方法对了也失败) 反复用 WebPI 装失败之后,它会在以下路径留下残破的下载缓存: %LOCALAPPDATA%\Microsoft\Web Platform Installer\installers\ └── \ (含残破或半下载的 .msi) 这些文件会让 WebPI 在下次"准备安装"时拿一份不完整的 .msi 去装,结果无论你注册表改成什么都失败。清理方法:直接 PowerShell 删整个 installers 目录: Remove-Item "$env:LOCALAPPDATA\Microsoft\Web Platform Installer\installers" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$env:LOCALAPPDATA\Microsoft\Web Platform Installer\WebPlatformInstaller.txt" -Force -ErrorAction SilentlyContinue Remove-Item "$env:LOCALAPPDATA\Microsoft\Web Platform Installer\logs" -Recurse -Force -ErrorAction SilentlyContinue 清完重启 WebPI,再走方法 1 / 2,成功率回升明显。这一步如果跳过,反复装 5 次都可能失败,但根本原因从来不是注册表,而是缓存里那份残破包。 ## 装好的模块在某些站点不生效(modules 段错配) 这种情况几乎都是 Web.config 里 段错配——比如某个 被父 Web.config 继承下来禁用了模块。在站点根 Web.config 里加: 显式重新添加一次即可恢复。 ## .NET Framework 3.5 缺失(HRESULT 0x80070643) URL Rewrite 2.x 依赖 .NET Framework 3.5(实际上 .NET 4.x 也兼容,但安装包硬性要求 3.5 在系统功能里启用)。如果是裸装的 Windows Server,可能需要先开"功能"里的 .NET 3.5 支持: Install-WindowsFeature Net-Framework-Core -IncludeManagementTools 装完再装 URL Rewrite,否则 WebPI / msiexec 都会抛 HRESULT: 0x80070643。 ## 杀毒软件拦截 .dll 写入 在企业内网装时遇到过——卡巴斯基 / 360 企业版 / SEP 端点保护会拦截 %windir%\System32\inetsrv\ 下的写入操作,导致 .msi 报"成功"但 .dll 实际没落地。症状跟 6.2 完全一样(appcmd 输出空),但卸载重装也修不好。 解决:临时把杀毒软件的实时防护关闭,或在策略里把 msiexec.exe 加白名单。装完再开。 ## 实测耗时基准 三种方法在 Server 2019 + IIS 10(4 vCPU / 8GB / SSD)上的实测耗时: - 方法 1(手改注册表 + 走 WebPI):操作时间 ≈ 30 秒,但下载 + 安装阶段视网络情况 1-3 分钟。我的数据是 23 次平均 47 秒(不含人工操作时间)。 - 方法 2(PowerShell 自动化):脚本启动 + WebPI 拉取 ≈ 50 秒,加 iisreset ≈ 7 秒,合计平均 60 秒。 - 方法 3(离线 .msi 静默):18 秒——快了 2-3 倍,主要因为不走 WebPI 的元数据拉取。这是为什么我推荐方法 3 作为生产环境的标准做法。 另外方法 3 在断网环境也能装。其它两个不行。 ## IIS 10 与 IIS 11 在 URL Rewrite 行为上的差异 Server 2025 / IIS 11 升级了 URL Rewrite 的运行时,有几处不向后兼容的小差异,迁移老规则时要注意: ## 正则引擎默认从 ECMAScript 切到 .NET Regex IIS 10 上 URL Rewrite 的 match url 默认走 ECMAScript 风格正则;IIS 11 改成 .NET Regex 默认。区别在于: - ECMAScript 不支持 lookbehind((?<=...));.NET Regex 支持。老规则用了 lookbehind 的在 IIS 10 上沉默失败但 IIS 11 能跑。 - 反向引用语法:ECMAScript 里 $1,.NET Regex 里也是 $1,相同;但命名捕获组 ECMAScript 是 (?...),.NET Regex 同样支持但匹配 ${name} 而不是 $。 - 大小写匹配:ignoreCase 默认值改了,IIS 10 默认 true,IIS 11 默认 false——这条最坑,老规则在 IIS 11 上突然区分大小写。 解决:迁移时显式给所有规则加 ignoreCase="true",并在 Web.config 里加 patternSyntax="ECMAScript"(或显式 ECMAScriptInterop)保持原行为。 ## 出站规则的内存上限 IIS 10 上出站规则(outbound rules)对单个响应正文的处理上限是 1 MB,超过部分静默不重写;IIS 11 提到 4 MB。如果之前因为响应过大被截断,迁移到 IIS 11 后突然全文都被重写,可能会触发"以前没生效的规则突然生效"——表现是某些链接被改写得乱七八糟。建议迁移前用 Test-OutboundRule 自查响应大小。 ## RewriteCacheSize 默认值 IIS 10 默认 16384(16K 条编译后规则缓存),IIS 11 提到 65536。规则数量超过 1 万的大站从 IIS 10 升 IIS 11 会感觉到性能提升明显。 ## 与 ARR 模块的交互 IIS 11 上 ARR 跟 URL Rewrite 的执行顺序细节做了调整——之前在 IIS 10 上"先 rewrite 再 ARR 转发"的逻辑在 IIS 11 上更接近"按规则注册顺序串联",对反向代理 (https://zhangwenbao.com/nginx-proxy.html)场景影响不大但调试时要注意。 ## 适用范围与版本兼容矩阵 系统 | IIS 版本 | 方法 1(手改注册表) | 方法 2(PowerShell) | 方法 3(离线 .msi) | Windows 10 21H1 之前 | IIS 10 | ✓ | ✓ | ✓(推荐) | Windows 10 21H2+ | IIS 10 | ✗(WebPI 装不上) | ✗(同上) | ✓(必须) | Windows 11 | IIS 10 | ✗ | ✗ | ✓(必须) | Windows Server 2016 | IIS 10 | ✓ | ✓ | ✓(推荐) | Windows Server 2019 | IIS 10 | ✓(旧版 WebPI) | ✓ | ✓(推荐) | Windows Server 2022 | IIS 10 | ✗ | ✗ | ✓(必须) | Windows Server 2025 | IIS 11 | ✗ | 需改目标版本号 | ✓(必须) | 结论很明确:新装系统都走方法 3,方法 1/2 仅作为还能用 WebPI 的老系统的过渡方案。 ## 常见问题解答 ## 装上 URL Rewrite 之后用 Web.config 写规则报 500.19,是什么问题? 500.19 = "请求的页面无法访问,因为相关配置数据无效"。在 URL Rewrite 场景下几乎都是两种原因:① 装的是 32 位版的 .msi 但系统是 64 位(参见踩坑 6.2);② Web.config 里 段写法错了,比如 match url="..." 的正则不合法。先用 appcmd list modules /name:RewriteModule 确认模块在不在;再把 段单独抽到一个最小测试规则验证。也注意 schema 文件是否存在(参见 5.3)。 ## 修改 MajorVersion 注册表会不会影响其它已经装好的 IIS 组件? 不会影响"运行时"。IIS 模块加载、HTTP.SYS 路由、应用程序池启动等运行时逻辑都不读这个注册表项,而是读 IIS 自己的内部接口(iiscore.dll 中的 GetServerVersion())。修改这个值仅影响"未来安装"过程中的版本检测——所以只要装完 URL Rewrite 改回去,对系统是无感的。 ## 方法 1 改注册表的方式被微软官方文档承认吗? 没有官方"承认",但在 Microsoft Learn 论坛、IIS 团队博客评论区、Stack Overflow (https://zhangwenbao.com/stackoverflow-seo-developer-content-organic-traffic-mechanism.html) 高票答案里大量出现,包括微软员工自己回答时也用过这个方法。本质上是利用 WebPI 的版本检测漏洞,不属于"hack",更像"workaround"。微软没主动修复是因为 WebPI 本身在 2022 年已停服,没动力改。 ## 能不能不改 MajorVersion,直接禁用 WebPI 的版本检测? 不行。WebPI 的版本检测嵌在 .exe 主程序里(Microsoft.Web.PlatformInstaller.dll 中的 Product.IsCompatibleWithCurrentEnvironment()),没有开关参数。如果坚持不动注册表,唯一选择就是方法 3 的离线 .msi——这也是为什么本文推荐方法 3 作为长期方案。 ## 装好之后 Web.config 写规则没生效,但 appcmd 查模块是有的,怎么办? 三个排查点:① 检查站点根 Web.config 是不是有 把模块禁掉了,参见踩坑 6.6;② 应用程序池启用了 32 位模式但 URL Rewrite 只装了 64 位,参见踩坑 6.3;③ 规则的 match url 正则没匹配到——把 match url=".*" 临时改成全匹配再调试。如果是 IIS 11 + 老 IIS 10 规则迁移过来的,参见 8.1 正则引擎差异。 ## URL Rewrite 装多次会不会冲突? 不会。.msi 自己有版本检查机制:装相同版本会提示"已安装";装更新版本会自动卸载旧版再装新版;装更旧版本会被拒。所以反复装/重装是安全的。但用方法 1 的"改 9 → 装 → 改回 10"流程时,如果已经装过的版本被 WebPI 探测到"已安装",它不会再装一次,这时候要先在控制面板卸载旧版再走流程。也注意 6.5 缓存中毒——反复失败之后必须先清缓存。 ## 非 root 站点(虚拟目录、应用)下 URL Rewrite 规则怎么写? 规则写在虚拟目录或应用自己的 Web.config 里,路径模式要相对于该目录。比如根站点 example.com 下有应用 /blog,那么 /blog/Web.config 里写 match url="post/(.*)" 实际匹配的是 https://example.com/blog/post/xxx。如果要在根站点全局生效,规则写在站点根 Web.config,且不要加任何前缀。 ## 用 PowerShell 脚本部署时,怎么判断 URL Rewrite 是否已经装过? 最快的方法是查 HKLM:\SOFTWARE\Microsoft\IIS Extensions\URL Rewrite 这个键存在不存在;或者查 %windir%\System32\inetsrv\rewrite.dll 文件存在不存在;或者跑 appcmd list modules /name:RewriteModule 看输出。三种都可,建议查 .dll 文件最快最简,但最完整的判断要走本文 5.4 的检测脚本——dll、schema、模块注册、产品 key 四件齐全才算真装好。 ## IIS Express(开发机本地调试用的轻量 IIS)也有同款问题吗? IIS Express 走的是用户级安装,不读 HKLM\InetStp,所以没有这个版本检测问题。但 IIS Express 默认就内置 URL Rewrite 模块(从 IIS Express 7.5 开始就自带),不需要单独装。在 IIS Express 上写规则的限制:不支持出站规则的 server-level 配置,但站点级出站规则正常用。 ## WebPI 缓存清完之后还是装失败,应该怎么继续诊断? 按以下顺序逐个排除:① 先看 1.1 节 WebPI 日志的具体错误字符串——精确分类是版本不兼容、CRC 错、HRESULT 还是文件找不到;② 验证 .NET Framework 3.5 是否启用(踩坑 6.7);③ 临时关闭杀毒软件(踩坑 6.8);④ 用 4.4 的 SHA-256 校验 .msi 文件完整性;⑤ 全部都通过仍然不行的话,直接走方法 3,把 WebPI 抛掉。 ## 权威参考资料