Reverse Proxy Websockets in IIS

mdodge 21 Reputation points
2023-04-14T21:58:16.94+00:00

I have a React web application with a NodeJS backend served up with IIS (looks like version 10 on Windows Server 2016). I have gotten the I am trying to figure out how to correctly reverse proxy my websocket calls. I have a second Node server running just for handling my websockets. I have that running over https with a self-signed certficate. So I have tested websocket calls to my websocket node server from Postman on my computer, a seperate computer from our web server. URL looks like: 'wss://example.domain.local:3010' and those work just fine. Now when I am on the webserver itself and I run the web app with with websocket calls to 'wss://localhost:3010', those work fine. So the issue happens when I run the web app on a remote client and it makes a websocket call to 'wss://example.domain.local:3010', it says "WebSocket connection failed". faled ws call

Here is my entire web.config file:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<location path="index.html">
		<system.webServer>
			<httpProtocol>
				<customHeaders>
					<add name="Cache-Control" value="no-store, max-age=0" />
				</customHeaders>
			</httpProtocol>
		</system.webServer>
	</location>
    <system.webServer>
        <rewrite>
            <rules>
                <clear />
                <rule name="WebSocketReverseProxyRule" enabled="true" stopProcessing="true">
					<match url="wss://example.domain.local:3010(.*)" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false"></conditions>
					<action type="Rewrite" url="wss://localhost:3010/{R:1}" />
					<serverVariables>
						<set name="HTTP_SEC_WEBSOCKET_EXTENSIONS" value="" />
					</serverVariables>
				 </rule>
                <rule name="HTTPS Redirect" enabled="true" 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="Redirect to HTTP" enabled="false" stopProcessing="true">
				  <match url="(.*)" />
				  <conditions>
					<add input="{HTTPS}" pattern="^ON$" />
				  </conditions>
				  <action type="Redirect" url="http://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
				</rule>
                <rule name="Static Assets" enabled="true" stopProcessing="true">
                    <match url="([\S]+[.](html|htm|svg|js|css|png|gif|jpg|jpeg|json))" />
                    <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:3008/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:3008/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:3008/{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>
            <outboundRules>
                <rule name="ReverseProxyOutboundRule1" preCondition="ResponseIsTextHtml" enabled="true" stopProcessing="false">
                    <match filterByTags="None" pattern="^(.*?)\s" />
                    <action type="Rewrite" value="{R:1}" />
                </rule>
                <rule name="RestoreAcceptEncoding" preCondition="NeedsRestoringAcceptEncoding" enabled="true">
                    <match serverVariable="HTTP_ACCEPT_ENCODING" pattern="^(.*)" />
                    <action type="Rewrite" value="{HTTP_X_ORIGINAL_ACCEPT_ENCODING}" />
                </rule>
                <rule name="AnchorTagsRule" preCondition="ResponseIsTextAnything" enabled="false">
                    <match pattern="href=(.*?)https://example.domain.local:3008/(.*?)\s" />
                    <action type="Rewrite" value="href={R:1}https://example.domain.local/{R:2}" />
                </rule>
                <rule name="FormTagsRule" preCondition="ResponseIsTextAnything" enabled="false">
                    <match pattern="action=(.*?)https://example.domain.local:3008/(.*?)\\" />
                    <action type="Rewrite" value="action={R:1}https://example.domain.local/{R:2}\" />
                </rule>
                <preConditions>
					<preCondition name="ResponseIsTextHtml">
                        <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" />
                    </preCondition>
                    <preCondition name="ResponseIsTextAnything">
                        <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/(.+)" />
                    </preCondition>
                    <preCondition name="NeedsRestoringAcceptEncoding">
                        <add input="{HTTP_X_ORIGINAL_ACCEPT_ENCODING}" pattern=".*" />
                    </preCondition>
                </preConditions>
            </outboundRules>
        </rewrite>
		<urlCompression doStaticCompression="false" />
        <tracing>
            <traceFailedRequests>
                <add path="*">
                    <traceAreas>
                        <add provider="WWW Server" areas="Rewrite,RequestRouting" verbosity="Verbose" />
                    </traceAreas>
                    <failureDefinitions statusCodes="200-399" />
                </add>
            </traceFailedRequests>
        </tracing>
    </system.webServer>
</configuration>

Internet Information Services
{count} votes

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.