大家好,我是保哥。这几年我先后帮十几位站长把Windows Server上的老站点从HTTP升级到HTTPS,几乎每一次都会被同一个问题卡住:证书装好了、443端口也通了,但访问 http://域名 依然能打开未加密版本,搜索引擎抓回的还是HTTP链接。问题的根源是IIS默认不会把旧协议跳到新协议,必须手动加一条301重定向规则。下面这篇笔记,是我在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。我个人偏爱后者,因为可以版本化、批量复制到多个站点。下面是我用了多年的最简模板:
<?xml version='1.0' encoding='UTF-8'?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name='HTTP to HTTPS redirect' stopProcessing='true'>
<match url='(.*)' />
<conditions>
<add input='{HTTPS}' pattern='^OFF$' />
</conditions>
<action type='Redirect' url='https://{HTTP_HOST}/{R:1}' redirectType='Permanent' />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>几个关键参数说明一下,避免你照抄之后改不动:
- 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)。优化后的写法是直接一步跳到目标:
<rule name='Non-www to www HTTPS' stopProcessing='true'>
<match url='(.*)' />
<conditions logicalGrouping='MatchAny'>
<add input='{HTTP_HOST}' pattern='^example\.com$' />
<add input='{HTTPS}' pattern='^OFF$' />
</conditions>
<action type='Redirect' url='https://www.example.com/{R:1}' redirectType='Permanent' />
</rule>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} 始终为OFF。解决办法是改用 {HTTP_X_FORWARDED_PROTO} 这个头来判断:
<conditions>
<add input='{HTTP_X_FORWARDED_PROTO}' pattern='^http$' />
</conditions>- 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 里加这段:
<httpProtocol>
<customHeaders>
<add name='Strict-Transport-Security' value='max-age=31536000; includeSubDomains; preload' />
</customHeaders>
</httpProtocol>三个参数:
- 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 请求,是更新内链/外链最直接的依据。
常见问题解答
可以用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重新提交,权重会顺利平移。我自己几个站点跳完之后,三周左右排名就完全恢复并略有上升。具体加速恢复的做法是登录百度站长平台提交 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 改造时必须一并处理。