IIS(Internet Information Services)是 Windows Server 自带的 Web 服务器,装完之后不用额外下载任何东西就能跑静态站点、ASP.NET 应用、做反向代理。相比 Nginx 和 Apache,IIS 的优势是和 Windows 生态深度集成(进程模型、身份验证、服务托管),缺点也是这个——配置项分散在图形界面、applicationHost.config 和 web.config 三处,新手经常改了这里不懂那里。
这篇从零开始走一遍:安装 Web 服务器角色 → 理解应用程序池的关键选项 → 建站点、绑域名、配 SSL → 设目录权限 → 装 URL 重写模块做 HTTP 跳转 HTTPS → 最后给出 500 / 403 / 404 三类错误的快速排查路径。命令在 Server 2016 / 2019 / 2022 上通用。
第一步:安装 IIS 角色
一条 PowerShell 命令搞定,装完不需要重启:
# 安装 Web 服务器角色并包含管理工具(管理器所在的那部分控制台)
Install-WindowsFeature Web-Server -IncludeManagementTools
# 常用附加角色的组合安装(按需要选,不要全装)
Install-WindowsFeature Web-Server, Web-Asp-Net45, Web-ISAPI-Ext, Web-ISAPI-Filter,
Web-Mgmt-Console, Web-Log-Libraries, Web-Request-Monitor, Web-Http-Tracing -IncludeManagementTools
# 卸载
Uninstall-WindowsFeature Web-Server
# 查看已安装的功能
Get-WindowsFeature | Where-Object Installed | Select-Object DisplayName, Name
-IncludeManagementTools 一定要带上,否则只装了服务没有 IIS 管理器(inetmgr)。Server Core 上也可以装,但图形管理器要用另外的机器做远程管理。
图形路径:服务器管理器 → 添加角色和功能 → 基于角色或基于功能的安装 → 服务器角色 → 勾选"Web 服务器(IIS)" → 角色服务按需勾选。
安装后验证:
iisreset /status
Get-Service W3SVC, WAS | Select-Object Name, Status
Start-Service W3SVC
# 浏览器访问 localhost 应看到默认欢迎页
Invoke-WebRequest http://localhost -UseBasicParsing | Select-Object StatusCode
装完之后马上可以访问 http://localhost,看到 IIS 的蓝色欢迎页说明服务正常。外部访问还需要确认防火墙和安全组放行了 80 / 443。
New-NetFirewallRule -DisplayName "IIS-HTTP" -Direction Inbound -Protocol TCP -LocalPort 80 -Action Allow -Profile Any
New-NetFirewallRule -DisplayName "IIS-HTTPS" -Direction Inbound -Protocol TCP -LocalPort 443 -Action Allow -Profile Any
第二步:理解应用程序池
应用程序池(Application Pool)是 IIS 的核心概念:它是 w3wp.exe 工作进程的容器,决定了你的站点跑在什么版本的 .NET 上、用什么身份访问磁盘、什么时候被回收。一个池下面挂站点,池一崩,下面所有站点一起挂。
创建和配置:
Import-Module WebAdministration
New-WebAppPool -Name "MyApp-Pool"
Set-ItemProperty "IIS:\AppPools\MyApp-Pool" -Name managedRuntimeVersion -Value "v4.0"
Set-ItemProperty "IIS:\AppPools\MyApp-Pool" -Name managedPipelineMode -Value "Integrated"
Set-ItemProperty "IIS:\AppPools\MyApp-Pool" -Name processModel.idleTimeout -Value "00:00:00"
Set-ItemProperty "IIS:\AppPools\MyApp-Pool" -Name recycling.periodicRestart.time -Value "00:00:00"
Get-ItemProperty "IIS:\AppPools\MyApp-Pool" | Select-Object *
关键选项该怎么选:
| 选项 | 取值 | 怎么选 |
|---|---|---|
| .NET CLR 版本 | v2.0 / v4.0 / 无托管代码 | ASP.NET 4.x 用 v4.0;ASP.NET Core 和纯静态、PHP 选"无托管代码" |
| 托管管道模式 | 集成 Integrated / 经典 Classic | 新站点一律 Integrated;跑老旧 WebForms 或需要 MapRequestHandler 的老组件时用 Classic |
| 标识 | ApplicationPoolIdentity / LocalSystem / NetworkService / 指定账户 | 默认 ApplicationPoolIdentity 最安全,访问网络共享时改用域账户 |
| 空闲超时 | 默认 20 分钟 | 企业站点建议调成 0(不回收),否则每天首次访问会慢 |
| 回收时间 | 默认 1740 分钟(29 小时) | 建议改到业务低峰的固定时间,避免高峰 recycle 造成 Session 丢失 |
| 快速故障保护 | 5 次失败 5 分钟内 | 程序异常状况下会自动停池,调试时留意症状,别只看站点挂了 |
关于回收,最常踩的坑是:站点莫名其妙每天早上变慢,其实是空闲超时导致进程被杀,第一个请求要重新 JIT 编译 + 建连接池。设置 idleTimeout=0 并且把回收排到凌晨,基本能解决。
再看一组实用的运维命令:
# 停止 / 启动某个池(不影响其他站点)
Stop-WebAppPool -Name "MyApp-Pool"
Start-WebAppPool -Name "MyApp-Pool"
Restart-WebAppPool -Name "MyApp-Pool"
# 查看该池下有几个工作进程(Web Garden)
Get-ItemProperty "IIS:\AppPools\MyApp-Pool" -Name processModel.maxProcesses
# 查看当前运行的 w3wp 及其所属池
Get-WmiObject Win32_Process -Filter "Name='w3wp.exe'" | Select-Object ProcessId, CommandLine
最后一行很有用,CommandLine 里带了 -ap "MyApp-Pool",可以直接把任务管理器里看到的高 CPU 进程对应到具体站点。
第三步:建站点、绑域名
推荐的做法是先把文件放到 D:\wwwroot\mysite,不要放在 C:\inetpub\wwwroot 下面和默认站点混在一起。
New-Item -ItemType Directory -Path D:\wwwroot\mysite -Force
# 把站点文件拷进去后:
New-IISSite -Name "mysite" -PhysicalPath "D:\wwwroot\mysite" -BindingInformation "*:80:www.example.com"
# 或者用老的 AppCmd / PowerShell 组合方式
New-Website -Name "mysite" -PhysicalPath "D:\wwwroot\mysite" -Port 80 -HostHeader "www.example.com" -ApplicationPool "MyApp-Pool"
Set-ItemProperty "IIS:\Sites\mysite" -Name applicationPool -Value "MyApp-Pool"
绑定(Binding)的三要素:IP 地址、端口、主机名。主机名留空表示匹配所有;填了主机名后,只有 Host 头匹配的请求才会落到这个站点,这也是一台服务器托管多个域名的基础。
| 绑定写法 | 含义 |
|---|---|
*:80: |
任意 IP、80 端口、任意主机名(兜底) |
*:80:www.example.com |
只接受 Host 为 www.example.com 的请求 |
10.0.0.10:8080: |
只绑定特定网卡 IP 的 8080 端口,通常用于内网或管理后台 |
*:443:www.example.com + SNI |
HTTPS,多域名共享 443 时必须开启 SNI |
一个域名同时有多个前缀(example.com 和 www.example.com)时,加第二条绑定即可:
New-IISSiteBinding -Name "mysite" -BindingInformation "*:80:example.com" -Protocol http
Get-IISSiteBinding -Name "mysite"
Remove-IISSiteBinding -Name "mysite" -BindingInformation "*:80:example.com" -Protocol http
HTTPS 绑定需要先有证书,导入后再绑:
# 导入 PFX 证书到本机个人存储
$pwd = ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force
Import-PfxCertificate -FilePath D:\cert\example.com.pfx -CertStoreLocation Cert:\LocalMachine\My -Password $pwd
# 创建 HTTPS 绑定并指定证书(需 thumbprint)
$thumb = (Get-ChildItem Cert:\LocalMachine\My | Where-Object Subject -like "*example.com*").Thumbprint
New-IISSiteBinding -Name "mysite" -BindingInformation "*:443:www.example.com" `
-Protocol https -SslFlag 1 -CertificateThumbPrint $thumb # SslFlag 1 = 启用 SNI
提示:多个 HTTPS 站点共用 443 时,必须勾选"需要服务器名称指示(SNI)"。不支持 SNI 的老客户端(Windows XP 上的 IE)会拿到证书告警,考虑到当前几乎没有这种流量,可以直接启用 SNI。
第四步:目录权限,这一环决定 403 还是正常
IIS 访问磁盘文件时用的是应用程序池标识。默认的 ApplicationPoolIdentity 在文件系统上表现为虚拟账户 IIS AppPool\MyApp-Pool。还有一个内置组 IIS_IUSRS,所有池标识默认都在里面。
给目录授权的正确做法:
$acl = Get-Acl D:\wwwroot\mysite
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
"IIS AppPool\MyApp-Pool", "Modify", "ContainerInherit,ObjectInherit", "None", "Allow")
$acl.AddAccessRule($rule)
Set-Acl D:\wwwroot\mysite $acl
# 或者直接对组授权(更简单,隔离性差一点)
icacls D:\wwwroot\mysite /grant "IIS_IUSRS:(OI)(CI)(M)" /T
icacls D:\wwwroot\mysite /grant "IIS AppPool\MyApp-Pool:(OI)(CI)(M)" /T
# 只看有哪些权限
icacls D:\wwwroot\mysite
权限给的粒度建议:
| 目录 | 推荐权限 | 原因 |
|---|---|---|
| 静态内容、程序 DLL 目录 | 读取 + 执行 | 防止被上传写入 Webshell |
| 上传目录 / App_Data | 修改(不含执行) | 允许写入但禁止运行脚本 |
| 日志目录 | 修改 | 应用需要落盘日志 |
| 临时目录 | 修改 | 某些框架需要 |
风险提示:不要用
Everyone:Full Control也不要把站点目录放到桌面或用户目录下图省事。上传目录要在 IIS 里额外禁用脚本执行权限:Set-WebConfigurationProperty -Filter /system.webServer/handlers ...或图形界面在"处理程序映射"里编辑功能权限为"只读"。
第五步:默认文档与 URL 重写
默认文档就是访问 / 时自动找哪个文件,IIS 默认有 Default.htm、Default.aspx、index.htm 等,但没有 index.php 或 index.html(小写 html 也没在默认列表里,Windows 文件系统不区分大小写所以实际能匹配)。
Get-WebConfigurationProperty -Filter "//defaultDocument/files" -PSPath "IIS:\Sites\mysite" -Name Collection
Add-WebConfigurationProperty -Filter "//defaultDocument/files" -PSPath "IIS:\Sites\mysite" -Name Collection -Value "index.php"
Add-WebConfigurationProperty -Filter "//defaultDocument/files" -PSPath "IIS:\Sites\mysite" -Name Collection -Value "index.html"
Remove-WebConfigurationProperty -Filter "//defaultDocument/files" -PSPath "IIS:\Sites\mysite" -Name Collection -Value "iisstart.htm"
URL 重写模块(URL Rewrite Module)需要额外下载安装,官方安装包名是 rewrite_amd64_zh-CN.msi(中文版)或 rewrite_amd64_en-US.msi。装完之后 inetmgr 里站点下会出现"URL 重写"图标,或者直接写 web.config:
风险提示:跳转规则用
redirectType="Permanent"(301)会被浏览器长期缓存。上线前先改成Found(302)验证没问题再改成 301,否则规则配错时用户端会有很长的缓存残留。
安装重写模块后用 PowerShell 验证:
Get-WebGlobalModule | Where-Object Name -eq "RewriteModule"
Invoke-WebRequest -Uri "http://www.example.com" -MaximumRedirection 0 -ErrorAction SilentlyContinue |
Select-Object StatusCode, Headers
第六步:500 / 403 / 404 怎么查
| 错误码 | 常见原因 | 怎么查 |
|---|---|---|
| 500.19 | web.config 配置错误、模块没装、节被父级锁定 | 页面本身会给出"配置文件"和"错误行号",或看 HTTPERR 日志 |
| 500.21 | 处理程序没匹配到,通常是 ASP.NET Core 没装 Hosting Bundle | 检查 AspNetCoreModuleV2 模块是否注册 |
| 500.23 | ASP.NET 未在集成模式下配置 | 确认 下有相应 handler |
| 403.14 | 目录浏览被禁用且没有默认文档 | 加默认文档,或临时启用目录浏览验证 |
| 401.3 | 文件 ACL 拒绝 | 给 IIS AppPool\<池名> 授权,见第四步 |
| 404.3 | MIME 类型缺失或处理程序未注册 | 加 MIME 类型或安装该功能 |
| 503 | 应用程序池停止 | Get-IISAppPool 看状态,可能是快速故障保护触发 |
日志位置:
# 站点访问日志
Get-ChildItem C:\inetpub\logs\LogFiles\W3SVC* -Recurse | Sort-Object LastWriteTime -Descending | Select-Object -First 5
# HTTP 错误日志(500 系列的细节常在这里)
Get-Content C:\Windows\System32\LogFiles\HTTPERR\httperr*.log -Tail 30
# Windows 事件
Get-EventLog -LogName Application -EntryType Error -Newest 20 |
Where-Object Source -like "*ASP.NET*" | Format-List *
通用诊断顺序:先看能否本地 curl http://localhost:端口 访问(能通说明是网络/绑定问题),再打开详细错误(IIS 管理器 → 错误页 → 编辑功能设置 → 详细错误),最后看 HTTPERR 日志里的 s-reason 字段。记得详情页只应该对内网 IP 显示,公网站点要改回"自定义错误页"。
# 允许详细错误仅限内网访问(改完记得恢复)
Set-WebConfigurationProperty -PSPath "IIS:\Sites\mysite" -Filter "/system.webServer/httpErrors" `
-Name errorMode -Value "DetailedLocalOnly"
Get-WebConfigurationProperty -PSPath "IIS:\Sites\mysite" -Filter "/system.webServer/httpErrors" -Name errorMode
常见问题
Q:装完 IIS 外网访问不了?
三件事:服务器上的 Windows 防火墙放行 80/443;云服务器安全组入方向放行 80/443;确认默认站点是否已占用端口,或者被新建站点的主机头抢占。用这些命令逐条验证:netstat -ano | findstr :80,Test-NetConnection 公网IP -Port 80。
Q:同一个 IP 想放两个网站?
用不同主机名(Host 头)区分,两个站点都绑 80 端口但主机名不同。HTTP/1.1 靠 Host 头路由,HTTPS 场景则需要 SNI。
Q:ASP.NET Core 站点报 500.21?
没有安装 .NET Core Hosting Bundle。装上后执行 iisreset,并确认应用程序池的 .NET CLR 版本 为"无托管代码",进程不带 CLR 才是托管宿主模型的正确姿势。
Q:修改了配置需要重启吗?
多数改动不需要。web.config 保存后立即生效(会自动回收该站点对应的 AppDomain);改应用程序池标识、站点绑定需要 restart 对应池;只有安装角色/模块才需要 iisreset 或重启。
Q:如何查看某个站点当前占多少内存?
Get-WmiObject Win32_Process -Filter "Name='w3wp.exe'" | Select-Object ProcessId,CommandLine,WorkingSetSize,按 CommandLine 里的池名对应站点。也可以用 perfmon 的 Process(w3wp*) 系列计数器做长期观察。
总结
- 安装用
Install-WindowsFeature Web-Server -IncludeManagementTools,别忘了防火墙和安全组两层放行 80/443。 - 应用程序池的两个高价值调整:
idleTimeout设为 0,回收时间挪到业务低峰,能显著改善"每天首访问慢"的现象。 - 绑定配置记住三要素:IP、端口、主机名;多 HTTPS 站点共享 443 必须开 SNI。
- 目录权限出问题大多是 500.19 或 401.3;给
IIS AppPool\<池名>授权,上传目录另加"禁止脚本执行"。 - HTTP 跳 HTTPS 先在 URLs 重写模块里跑 302 验证,确认无误再切 301;详细错误页只在内网开放。
企业QQ咨询




