配置反向代理的HTTPS IIS站点中Secure WebSocket连接失败
问题分析与解决方案
你的WebSocket反向代理配置核心错误在于match url匹配规则不符合IIS Rewrite逻辑:IIS的<match url>仅匹配请求的路径部分(不含协议、域名),但你写了完整的wss://example.domain.local/ws(.*),导致规则无法命中实际请求。同时,WebSocket握手需要特定HTTP头支持,原规则缺少相关判断与头传递配置。
修正后的WebSocket反向代理规则
替换原web.config中的WS reverse proxy规则为以下内容:
<rule name="WS reverse proxy" enabled="true" stopProcessing="true"> <match url="^ws(.*)" /> <conditions> <add input="{HTTP_UPGRADE}" pattern="^WebSocket$" /> <add input="{HTTP_CONNECTION}" pattern="^Upgrade$" /> </conditions> <serverVariables> <set name="HTTP_UPGRADE" value="websocket" /> <set name="HTTP_CONNECTION" value="Upgrade" /> <set name="HTTP_SEC_WEBSOCKET_EXTENSIONS" value="{HTTP_SEC_WEBSOCKET_EXTENSIONS}" /> </serverVariables> <action type="Rewrite" url="ws://example.domain.local:3009/{R:1}" /> </rule>
关键修正点说明
- 正确匹配请求路径:
^ws(.*)仅匹配以ws开头的请求路径(对应前端发起的wss://example.domain.local/ws请求),符合IIS Rewrite的匹配规则。 - 精准识别WebSocket握手:通过
HTTP_UPGRADE和HTTP_CONNECTION头判断,确保仅处理WebSocket握手请求,避免误匹配其他类型请求。 - 传递必要握手头:将WebSocket握手必需的
Upgrade、Connection头原样传递给后端Node服务,同时保留SEC_WEBSOCKET_EXTENSIONS头(原配置清空该头可能导致握手失败)。
额外注意事项
- 确保IIS已安装Application Request Routing (ARR) 和WebSocket Protocol模块:
- ARR是反向代理核心组件,需通过IIS管理器的“Web平台安装程序”搜索安装。
- WebSocket模块需在服务器管理器的“添加角色和功能”中,勾选“Web服务器->应用程序开发”下的“WebSocket协议”。
- 规则顺序:保持
WS reverse proxy规则在所有规则最顶部,确保WebSocket请求优先被处理,不会被静态资源、React路由等规则拦截。
修正后的完整rules段
若需要替换整个rules部分,可参考以下内容:
<rules> <clear /> <rule name="WS reverse proxy" enabled="true" stopProcessing="true"> <match url="^ws(.*)" /> <conditions> <add input="{HTTP_UPGRADE}" pattern="^WebSocket$" /> <add input="{HTTP_CONNECTION}" pattern="^Upgrade$" /> </conditions> <serverVariables> <set name="HTTP_UPGRADE" value="websocket" /> <set name="HTTP_CONNECTION" value="Upgrade" /> <set name="HTTP_SEC_WEBSOCKET_EXTENSIONS" value="{HTTP_SEC_WEBSOCKET_EXTENSIONS}" /> </serverVariables> <action type="Rewrite" url="ws://example.domain.local:3009/{R:1}" /> </rule> <rule name="HTTPS Redirect" stopProcessing="true"> <match url="(.*)" /> <conditions logicalGrouping="MatchAll" trackAllCaptures="false"> <add input="{HTTPS}" pattern="^OFF$" /> </conditions> <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" appendQueryString="false" /> </rule> <rule name="Static Assets" enabled="true" stopProcessing="true"> <match url="([\S]+[.](html|htm|svg|js|css|png|gif|jpg|jpeg))" /> <conditions logicalGrouping="MatchAll" trackAllCaptures="false" /> <action type="Rewrite" url="/{R:1}" /> </rule> <rule name="Reverse Proxy Thumbnails to Node Server" enabled="true" stopProcessing="true"> <match url="^thumbs/(.*)" /> <conditions logicalGrouping="MatchAll" trackAllCaptures="false" /> <serverVariables> <set name="HTTP_X_ORIGINAL_ACCEPT_ENCODING" value="{HTTP_ACCEPT_ENCODING}" /> <set name="HTTP_ACCEPT_ENCODING" value="" /> </serverVariables> <action type="Rewrite" url="http://example.domain.local:3009/thumbs/{R:1}" /> </rule> <rule name="Reverse Proxy Images to Node Server" enabled="true" stopProcessing="true"> <match url="^images/(.*)" /> <conditions logicalGrouping="MatchAll" trackAllCaptures="false" /> <serverVariables> <set name="HTTP_X_ORIGINAL_ACCEPT_ENCODING" value="{HTTP_ACCEPT_ENCODING}" /> <set name="HTTP_ACCEPT_ENCODING" value="" /> </serverVariables> <action type="Rewrite" url="http://example.domain.local:3009/images/{R:1}" /> </rule> <rule name="Reverse Proxy API Calls to Node Server" enabled="true" stopProcessing="true"> <match url="^api/(.*)" /> <conditions logicalGrouping="MatchAll" trackAllCaptures="false" /> <serverVariables> <set name="HTTP_X_ORIGINAL_ACCEPT_ENCODING" value="{HTTP_ACCEPT_ENCODING}" /> <set name="HTTP_ACCEPT_ENCODING" value="" /> </serverVariables> <action type="Rewrite" url="http://example.domain.local:3009/{R:1}" logRewrittenUrl="true" /> </rule> <rule name="React Routes" stopProcessing="true"> <match url=".*" /> <conditions logicalGrouping="MatchAll" trackAllCaptures="false"> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> <add input="{REQUEST_URI}" matchType="Pattern" pattern="api/(.*)" negate="true" /> </conditions> <action type="Rewrite" url="/index.html" /> </rule> <!--rule name="SSL" patternSyntax="ExactMatch" stopProcessing="true"> <match url="*" /> <conditions> <add input="{HTTPS}" pattern="On" /> </conditions> <action type="Rewrite" url="https://example.domain.local/{R:1}" /> </rule--> <!-- this rule will route all requests through the index.html so React can load the requested URL correctly --> </rules>
内容的提问来源于stack exchange,提问作者dmikester1
相关产品推荐
相关产品推荐

