开发把站点包发过来了,说“部署一下就行”。你在 Windows Server 上装好 IIS,新建站点,浏览器打开却看到 403;改成 HTTPS 后变成 404;上传功能报“拒绝访问”;好不容易能访问了,第二天发现日志把 C 盘写满了。IIS(Internet Information Services)本身并不复杂,但站点绑定、证书、权限这三件事只要有一件没配对,表现出的错误又都长得差不多,于是排查成本陡增。
本文按真实部署顺序展开:安装角色 → 建应用池与站点 → 配置绑定(含 SNI 多证书)→ 申请与绑定 SSL 证书 → 配置 NTFS 权限与应用池标识 → 常见错误速查。所有操作同时给出图形入口(inetmgr)和命令行(appcmd / PowerShell),方便写进部署脚本。
一、安装 IIS 角色与必要组件
图形入口:服务器管理器(servermanager.exe)→ 添加角色和功能 → 基于角色或基于功能的安装 → 服务器角色 → 勾选“Web 服务器(IIS)”。关键是角色服务不要只勾默认项,ASP.NET 站点必须勾选“应用程序开发”下的对应版本。
# PowerShell 安装 Web 服务器(含管理工具)
Install-WindowsFeature -Name Web-Server -IncludeManagementTools
# 安装 ASP.NET 4.x 与常见组件(按需组合)
Install-WindowsFeature -Name `
Web-Asp-Net45, Web-Net-Ext45, Web-ISAPI-Ext, Web-ISAPI-Filter, `
Web-Default-Doc, Web-Dir-Browsing, Web-Http-Errors, Web-Static-Content, `
Web-Http-Redirect, Web-Http-Logging, Web-Log-Libraries, Web-Request-Monitor, `
Web-Http-Tracing, Web-Filtering, Web-Url-Auth, Web-Basic-Auth, `
Web-Windows-Auth, Web-Digest-Auth, Web-Client-Auth, Web-Mgmt-Console
# 确认安装结果
Get-WindowsFeature | Where-Object { $_.Name -like 'Web-*' -and $_.Installed } |
Select-Object Name, DisplayName | Format-Table -AutoSize
REM DISM 等价方式(脱机映像或 Server Core 常用) DISM /Online /Enable-Feature /FeatureName:IIS-WebServerRole /FeatureName:IIS-WebServer /FeatureName:IIS-ManagementConsole /FeatureName:IIS-ASPNET45 DISM /Online /Get-Features /Format:Table | findstr IIS
验证安装:浏览器访问 http://localhost,出现 IIS 欢迎页说明成功(默认站点 Default Web Site 占用 80 端口、站点 ID 为 1)。远程访问不通时,先放行防火墙:
REM 放行 HTTP / HTTPS netsh advfirewall firewall add rule name="Web-HTTP-80" dir=in action=allow protocol=TCP localport=80 netsh advfirewall firewall add rule name="Web-HTTPS-443" dir=in action=allow protocol=TCP localport=443
二、创建应用池与站点
强烈建议先建应用池再建站点,一个站点一个池,避免互相拖累。图形路径:inetmgr → 应用程序池 → 添加应用程序池 → 设置 .NET CLR 版本(v4.0 或无托管代码)与托管管道模式(集成 Integrated)。
2.1 appcmd 方式(CMD,最通用)
REM 切换到 inetsrv 目录 cd /d %windir%\system32\inetsrv REM 创建应用池(无托管代码站点用 v4.0 也无妨,纯静态/Node/Python 选"无托管代码") appcmd add apppool /name:MySitePool /managedRuntimeVersion:v4.0 /managedPipelineMode:Integrated REM 创建站点并指定物理路径与绑定 appcmd add site /name:"MySite" /id:10 /physicalPath:"D:\www\mysite" /bindings:http/*:80:www.example.com REM 把站点指定到应用池 appcmd set site /site.name:"MySite" /[path='/'].applicationPool:MySitePool REM 设置默认文档 appcmd set config "MySite" /section:defaultDocument /+files.[value='index.html'] REM 启动/停止/回收 appcmd start site "MySite" appcmd stop site "MySite" appcmd recycle apppool /apppool.name:MySitePool REM 查看站点、应用池、工作进程 appcmd list site appcmd list apppool appcmd list wp
2.2 PowerShell 方式
# 使用老的 WebAdministration 模块(Server 2012 R2 / IIS 8.5 及以前) Import-Module WebAdministration New-WebAppPool -Name "MySitePool" Set-ItemProperty IIS:\AppPools\MySitePool -Name managedRuntimeVersion -Value "v4.0" Set-ItemProperty IIS:\AppPools\MySitePool -Name processModel.identityType -Value ApplicationPoolIdentity New-WebSite -Name "MySite" -Port 80 -HostHeader "www.example.com" -PhysicalPath "D:\www\mysite" -ApplicationPool "MySitePool" Start-WebSite -Name "MySite" Get-Website | Select-Object Name, ID, State, PhysicalPath, Bindings Get-IISSite
# 使用新的 IISAdministration 模块(Server 2016+ / IIS 10 推荐)
Import-Module IISAdministration
New-IISAppPool -Name "MySitePool"
New-IISSite -Name "MySite" -PhysicalPath "D:\www\mysite" `
-BindingInformation "*:80:www.example.com" -Protocol http
Get-IISAppPool | Select-Object Name, Status, @{n='Runtime';e={$_.ManagedRuntimeVersion}}
三、站点绑定:端口、主机头与 SNI
绑定(Binding)决定“什么样的请求会被这个站点接收”,由 协议 + IP + 端口 + 主机头(Host Header)四元组组成。一台服务器用 80 端口承载多个域名,靠的就是主机头。
| 绑定示例 | 含义 | 适用场景 |
|---|---|---|
| http/*:80:(主机头为空) | 该 IP 上 80 端口的所有请求 | 单站点,或作为默认兜底站点 |
| http/*:80:www.a.com | 仅匹配 Host 为 www.a.com 的请求 | 多域名共用 80 端口(最常用) |
| http/10.0.0.5:80: | 仅监听指定网卡 IP | 多网卡服务器、内外网分离 |
| https/*:443:www.a.com | HTTPS + 主机头 | 需配合证书与 SNI |
| https/*:443:www.a.com(SNI 勾选) | 同一 IP:443 绑定多张证书 | 多 HTTPS 域名共用一 IP |
REM 追加一条 https 绑定(SslFlags: 0=IP 绑定 1=SNI 2=中央证书存储) appcmd set site /site.name:"MySite" /+bindings.[protocol='https',bindingInformation='*:443:www.example.com',sslFlags='1'] REM 修改已有绑定 appcmd set site /site.name:"MySite" /bindings.[protocol='http',bindingInformation='*:80:'].bindingInformation:'*:80:www.newdomain.com' REM 删除绑定 appcmd set site /site.name:"MySite" /-bindings.[protocol='https',bindingInformation='*:443:old.example.com']
# PowerShell:添加 / 查看绑定 New-WebBinding -Name "MySite" -Protocol https -Port 443 -HostHeader "www.example.com" -SslFlags 1 New-WebBinding -Name "MySite" -Protocol http -Port 80 -HostHeader "example.com" Get-WebBinding -Name "MySite" | Select-Object protocol, bindingInformation, sslFlags Remove-WebBinding -Name "MySite" -BindingInformation "*:80:old.example.com"
四、证书:申请、导入与 HTTPS 绑定
4.1 生成 CSR 并提交 CA
图形入口:inetmgr → 服务器节点 → 服务器证书 → 右侧“创建证书申请”。填写通用名称(必须与域名一致)、组织信息、加密位长(RSA 2048 起步),生成 CSR 文本文件后提交给证书颁发机构。
REM 用 certreq 生成 CSR:先准备 request.inf REM [NewRequest] REM Subject = "CN=www.example.com, OU=IT, O=Example Inc, C=CN" REM KeyLength = 2048 REM KeySpec = 1 REM MachineKeySet = TRUE REM Exportable = TRUE certreq -new request.inf csr.txt REM 查看生成的请求、提交给 CA、接受返回的证书 certreq -submit csr.txt cert.cer certreq -accept cert.cer REM 查看本机证书存储 certutil -store My
# 生成自签名证书(仅测试环境) New-SelfSignedCertificate -DnsName "www.example.com","example.com" ` -CertStoreLocation "cert:\LocalMachine\My" -FriendlyName "MySite-SSL" ` -NotAfter (Get-Date).AddYears(1) # 查看证书指纹(绑定时需要) Get-ChildItem cert:\LocalMachine\My | Select-Object Subject, FriendlyName, NotAfter, Thumbprint | Format-List
4.2 完成申请并绑定到站点
REM 把 CA 返回的证书安装到"个人/My"存储
certreq -accept cert.cer
REM 老系统(Server 2008 R2)用 netsh 绑定证书到 IP:Port
netsh http add sslcert ipport=0.0.0.0:443 certhash=0123456789abcdef0123456789abcdef01234567 appid={5d89a20c-beab-4389-9447-324788eb944a}
REM 查看 / 删除绑定
netsh http show sslcert
netsh http delete sslcert ipport=0.0.0.0:443
在 IIS 管理器里绑定更简单:选中站点 → 右侧“绑定” → 添加 → 类型 https → 主机名填域名 → 勾选“需要服务器名称指示(SNI)” → 在 SSL 证书下拉框选择证书。若下拉框为空,说明证书没有私钥或不在“个人”存储中,需要先用 certreq -accept 或 certlm.msc 导入(证书管理器:运行 mmc → 添加/删除管理单元 → 证书 → 计算机账户 → 本地计算机)。
证书续期提醒:Let's Encrypt 等免费证书 90 天有效,建议用 win-acme 之类的工具配合任务计划程序(taskschd.msc)自动续期。证书过期是 HTTPS 站点最常见的“突然打不开”。
五、权限设置:应用池标识与 NTFS 权限
这是最多人卡住的一环。IIS 访问磁盘文件时,使用的身份是应用池标识(处理动态内容)与 IUSR(匿名访问静态文件),而不是登录服务器的管理员账号。默认的应用池标识 ApplicationPoolIdentity 是一个虚拟账号,名称格式为 IIS AppPool\<池名>。
| 标识类型 | 说明 | 优点 | 缺点 |
|---|---|---|---|
| ApplicationPoolIdentity(默认,推荐) | 虚拟账号 IIS AppPool\池名 | 隔离性好、无需管密码 | 访问网络共享时身份为机器账号 |
| NetworkService | 内置账号,域中以计算机账号身份访问网络 | 可访问网络资源 | 权限较宽,隔离性差 |
| LocalService | 低权限本地账号 | 权限最小 | 网络访问受限,不常用 |
| LocalSystem | 最高权限 | 几乎不会有权限问题 | 安全风险极大,禁止使用 |
| SpecificUser(自定义域账号) | 指定账号 | 便于访问域内共享与数据库 | 需管理密码,改密后站点起不来 |
REM 给应用池标识授予站点目录读取与执行权限(RX) icacls "D:\www\mysite" /grant "IIS AppPool\MySitePool:(OI)(CI)(RX)" /T /Q REM 给上传/日志等需要写入的目录授予修改权限(M) icacls "D:\www\mysite\upload" /grant "IIS AppPool\MySitePool:(OI)(CI)(M)" /T /Q REM 静态文件匿名访问还需 IUSR 读取权限 icacls "D:\www\mysite" /grant "IUSR:(OI)(CI)(RX)" /T /Q REM 查看当前权限 icacls "D:\www\mysite"
# 修改应用池标识为指定账号(需输入密码) appcmd set config /section:applicationPools /[name='MySitePool'].processModel.identityType:SpecificUser appcmd set config /section:applicationPools /[name='MySitePool'].processModel.userName:DOMAIN\svc_web appcmd set config /section:applicationPools /[name='MySitePool'].processModel.password:P@ssw0rd # 设置闲置超时与回收(避免默认 20 分钟回收导致会话丢失) Set-ItemProperty IIS:\AppPools\MySitePool -Name processModel.idleTimeout -Value ([TimeSpan]::FromMinutes(0)) Set-ItemProperty IIS:\AppPools\MySitePool -Name recycling.periodicRestart.time -Value ([TimeSpan]::FromMinutes(0))
身份验证配置(inetmgr → 站点 → 身份验证):匿名身份验证默认使用 IUSR;Windows 身份验证需禁用匿名;ASP.NET 模拟(impersonation)开启后,会以访问者身份访问资源,配置复杂,非必要不开。
六、常见错误速查
| 错误 | 常见原因 | 处理 |
|---|---|---|
| 403.14 Forbidden | 目录浏览关闭且无默认文档 | 添加 index.html / default.aspx 为默认文档 |
| 401.3 Unauthorized | NTFS 权限不足 | 用 icacls 给应用池标识或 IUSR 授权 |
| 500.19 配置错误 | web.config 节点未解锁或模块未安装 | 看错误详情里的 configSource,安装对应角色服务 |
| 500.21 处理程序未注册 | 未安装 ASP.NET / .NET Core 托管捆绑包 | 安装 Web-Asp-Net45 或 Hosting Bundle 后重启 |
| 503 Service Unavailable | 应用池已停止或连续崩溃 | 启动应用池;查应用程序日志与快速失败保护 |
| 404 找不到 | 主机头不匹配、物理路径错、绑定端口错 | 核对绑定与 appcmd list site |
| 站点无法启动“端口被占用” | 80/443 被其他进程或 HTTP.SYS 保留 | netsh http show servicestate view=requestq |
| HTTPS 证书下拉为空 | 证书无私钥或不在个人存储 | 用 certlm.msc 重新导入 pfx(含私钥) |
REM 查看 HTTP.SYS 的 URL 保留与监听队列(端口占用排查神器) netsh http show servicestate view=requestq netsh http show urlacl netsh http show iplisten REM 查看 IIS 站点日志目录(HTTP 状态码 + 子状态码是定位关键) cd /d C:\inetpub\logs\LogFiles dir appcmd list site
# 查看最近的应用池崩溃与 WAS 日志
Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='Microsoft-Windows-WAS'} -MaxEvents 20 |
Select-Object TimeCreated, Id, LevelDisplayName, Message
# 统计日志中各类状态码出现次数
Get-ChildItem "C:\inetpub\logs\LogFiles\W3SVC10" -Filter *.log |
Get-Content | ForEach-Object { ($_ -split ' ')[-4] } |
Group-Object | Sort-Object Count -Descending | Select-Object -First 10 Count, Name
七、部署步骤清单
- 安装 Web-Server 角色及所需角色服务(ASP.NET、ISAPI、日志、跟踪等)。
- 放行防火墙 80 / 443,云主机同时配置安全组。
- 规划目录结构,站点根目录放在非系统盘(如
D:\www\)。 - 为每个站点创建独立应用池,设置 .NET 版本与管道模式。
- 创建站点,配置绑定(IP、端口、主机头)。
- 用 icacls 给
IIS AppPool\<池名>授予读取执行权限,写目录单独授予修改权限。 - 配置默认文档与身份验证方式。
- 生成 CSR → 申请证书 →
certreq -accept导入 → 绑定 HTTPS(多域名启用 SNI)。 - 把 IIS 日志目录改到数据盘,配置日志滚动与归档清理。
- 本地
curl http://localhost验证,再从外网访问验证,最后检查 HTTPS 证书链与重定向。 - 配置监控:站点可用性、应用池状态、磁盘余量、证书到期天数。
小结与避坑建议
Windows 服务器配置 IIS 网站,把“应用池 → 站点 → 绑定 → 证书 → 权限”这条链路走顺,八成的坑就绕开了。记住三条命令基本能自证:appcmd list site 看站点与 ID、netsh http show servicestate view=requestq 看端口占用、icacls 看文件权限。
实战中最值得记牢的几条:
- 一个站点一个应用池。共用一个池,某个站点内存泄漏会拖垮全部站点。
- 不要用 LocalSystem 跑应用池。权限问题是解决了,但一次上传漏洞就等于整台服务器沦陷。默认 ApplicationPoolIdentity 加精确 NTFS 授权才是正解。
- 给写入权限要精确到目录。整个站点给 Modify 等于给攻击者开后门,只有 upload、cache、logs 这类目录需要写权限。
- 绑定主机头不要留空。留空的站点会抢走该 IP:端口上所有未匹配的请求,导致其他站点“莫名 404”。
- 证书一定要含私钥。只导入 .cer 公钥证书,IIS 的 SSL 证书下拉框会是空的,此时用
certlm.msc重新导入包含私钥的 .pfx。 - 把 IIS 日志迁出系统盘。默认
C:\inetpub\logs,高流量站点几天就能写满 C 盘,改目录用appcmd set config -section:system.applicationHost/sites -siteDefaults.logFile.directory:"D:\IISLogs"。 - 关注证书到期。配置自动续期或至少提前 30 天告警,证书过期导致的业务中断在行业里每年都在重演。
- 改配置前先备份。
appcmd add backup "before-change"一条命令,回滚时appcmd restore backup "before-change",比手动改回去可靠得多。
企业QQ咨询




