0

I'm trying to get a websocket based website to work that is being reverse proxied by an IIS server.

Situation

  • IIS v10.0.19041.1 running on Windows 10 Pro.
  • SSL certificate from LetsEncrypt and installed/managed by Win-ACME into IIS.
  • FoundryVTT v0.7.3 dedicated server running in a FreeNAS 11.4-RELEASE-p2 jail (this is the websocket based website).
  • FoundryVTT uses socket.io for node.js.
  • FoundryVTT server is at IP 192.168.2.36 and Port 30000.

Observation

  • The FoundryVTT server works fine on LAN.
  • The SSL certificate is valid and working and the server is approachable from WAN.
  • Everything works fine upon loading the FoundryVTT set-up page via WAN, however as soon as I've entered my Admin Access Key I'm presented with an empty set-up page. The FoundryVTT community mentions that this is a known issue with wrongly configured proxies. Empty Setup page
  • Upon Observing the web traffic using Fiddler v5.0.2020.18177 I observe that I do get to the point of a 101 HTTP response to switch protocol to websocket.Fiddler websocket response
  • Double-clicking the capture does not let me go to the websocket tab to inspect traffic there. I suspect that no connection is made at all as I see several more 101 switch protocol responses after the initial first one with slight delays in between.

What have I tried

  • I have verified that I have the WebSocket Protocol feature installed. Windows Features - WebSocket Protocol

  • I have verified that websockets are enabled on my website in IIS. IIS - Configuration Editor - webSocket

  • I have tried messing around with the web.config by following various suggestions people made on the internet (i.e. this, this and this). web.config as it is now:

      <?xml version="1.0" encoding="UTF-8"?>
      <configuration>
          <system.webServer>
              <rewrite>
                  <rules>
                      <clear />
                      <rule name="Web Socket Reverse" enabled="true" stopProcessing="true">
                          <match url="ws:///example.com:30000(.*)" />
                          <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                          </conditions>
                          <action type="Rewrite" url="ws://192.168.2.36:30000/{R:1}" />
                      </rule>
                      <rule name="Web Socket Reverse 2" enabled="true" stopProcessing="true">
                          <match url="wss://example.com:30000(.*)" />
                          <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                          </conditions>
                          <action type="Rewrite" url="wss://192.168.2.36:30000/{R:1}" />
                      </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}" />
                      </rule>     
                      <rule name="FoundryVTT proxy" stopProcessing="true">
                        <match url="(.*)" />
                        <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                          <add input="{HTTP_HOST}" pattern="example.com" />
                        </conditions>
                        <action type="Rewrite" url="http://192.168.2.36:30000/{R:1}" />
                        <serverVariables>
                          <set name="HTTP_X_ORIGINAL_ACCEPT_ENCODING" value="{HTTP_ACCEPT_ENCODING}" />
                          <set name="HTTP_ACCEPT_ENCODING" value="" />
                        </serverVariables>
                      </rule>
                      <rule name="RequestBlockingRule1" enabled="true" patternSyntax="Wildcard" stopProcessing="true">
                          <match url="*" />
                          <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                              <add input="{URL}" pattern="*" />
                              <add input="{HTTP_HOST}" pattern="example.com" negate="true" />
                          </conditions>
                          <action type="CustomResponse" statusCode="403" statusReason="Forbidden: Access is denied." statusDescription="You do not have permission to view this directory or page using the credentials that you supplied." />
                      </rule>
                  </rules>
                  <outboundRules>
                      <rule name="RestoreAcceptEncoding" preCondition="NeedsRestoringAcceptEncoding">
                        <match serverVariable="HTTP_ACCEPT_ENCODING" pattern="^(.*)" />
                        <conditions logicalGrouping="MatchAll" trackAllCaptures="true" />
                        <action type="Rewrite" value="{HTTP_X_ORIGINAL_ACCEPT_ENCODING}" />
                      </rule>
                      <rule name="ReverseProxyOutboundRule1" preCondition="ResponseIsHtml1">
                          <match filterByTags="A, Form, Img" pattern="^http(s)?://192.168.2.36:30000/(.*)" />
                          <action type="Rewrite" value="http{R:1}://example.com/{R:2}" />
                      </rule>
                      <preConditions>
                        <preCondition name="ResponseIsHtml1">
                          <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" />
                        </preCondition>
                        <preCondition name="NeedsRestoringAcceptEncoding">
                          <add input="{HTTP_X_ORIGINAL_ACCEPT_ENCODING}" pattern=".+" />
                        </preCondition>
                      </preConditions>
                    </outboundRules>
                  <rewriteMaps>
                      <!--{MapProtocol:{HTTPS}}-->
                      <rewriteMap name="MapProtocol">
                          <add key="on" value="https" />
                          <add key="off" value="http" />
                      </rewriteMap>
                  </rewriteMaps>
              </rewrite>
              <urlCompression doStaticCompression="false" doDynamicCompression="false" />
          </system.webServer>
      </configuration>
    
  • I've restarted the IIS website, used incognito mode in my browser, disabled add-ons and used a different browser.

  • I've looked at guides on how to configure it for NGINX, Caddy and Apache as inspiritation (IIS is not present on the wiki).

  • I've asked around on their Discord chat, but nobody seems to know enough about IIS.

A snippet from the IIS log when visiting the FoundryVTT website:

    2020-10-12 16:04:13 192.168.2.11 POST /setup X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=8c5986c1-768f-4833-b71b-04ed4bae47f1&SERVER-STATUS=302 443 - SOMEHOST.net Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/86.0.4240.75+Safari/537.36 https://example.com/setup 302 0 0 35
    2020-10-12 16:04:13 192.168.2.11 GET /setup X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=fddb90ae-27b7-4cce-b3b0-a6864d451514&SERVER-STATUS=200 443 - SOMEHOST.net Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/86.0.4240.75+Safari/537.36 https://example.com/setup 200 0 0 25
    2020-10-12 16:04:13 192.168.2.11 GET /css/style.css X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=0a2d276f-6985-4fd7-9d21-1e4f63cacb80&SERVER-STATUS=304 443 - SOMEHOST.net Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/86.0.4240.75+Safari/537.36 https://example.com/setup 304 0 0 26
    2020-10-12 16:04:13 192.168.2.11 GET /fonts/fontawesome/css/all.min.css X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=78f40fa3-22bd-47ac-8987-03ec7ea70a5d&SERVER-STATUS=304 443 - SOMEHOST.net Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/86.0.4240.75+Safari/537.36 https://example.com/setup 304 0 0 23
    2020-10-12 16:04:13 192.168.2.11 GET /scripts/jquery.min.js X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=f94993ac-23a7-4b71-8db9-45b564c91a40&SERVER-STATUS=304 443 - SOMEHOST.net Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/86.0.4240.75+Safari/537.36 https://example.com/setup 304 0 0 22
    2020-10-12 16:04:13 192.168.2.11 GET /scripts/handlebars.min.js X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=cf0118b0-6a3d-4fb1-8654-abfbcfc6af35&SERVER-STATUS=304 443 - SOMEHOST.net Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/86.0.4240.75+Safari/537.36 https://example.com/setup 304 0 0 21
    2020-10-12 16:04:13 192.168.2.11 GET /scripts/handlebars-intl.min.js X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=b76f116d-213f-44b8-9479-8ed79157c623&SERVER-STATUS=304 443 - SOMEHOST.net Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/86.0.4240.75+Safari/537.36 https://example.com/setup 304 0 0 21
    2020-10-12 16:04:13 192.168.2.11 GET /scripts/foundry.js X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=94e56a90-ae3e-4095-bb40-00ae04033be1&SERVER-STATUS=304 443 - SOMEHOST.net Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/86.0.4240.75+Safari/537.36 https://example.com/setup 304 0 0 27
    2020-10-12 16:04:13 192.168.2.11 GET /scripts/howler.min.js X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=31c85a47-e7f8-40e6-b242-79377bb9136f&SERVER-STATUS=304 443 - SOMEHOST.net Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/86.0.4240.75+Safari/537.36 https://example.com/setup 304 0 0 27
    2020-10-12 16:04:13 192.168.2.11 GET /scripts/pixi.min.js X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=ffb1b3d7-00cf-4d68-8cf9-e3e87bf6b811&SERVER-STATUS=304 443 - SOMEHOST.net Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/86.0.4240.75+Safari/537.36 https://example.com/setup 304 0 0 27
    2020-10-12 16:04:13 192.168.2.11 GET /scripts/socket.io.slim.js X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=4f937d01-ead6-437c-9e4e-fc050ccd2556&SERVER-STATUS=304 443 - SOMEHOST.net Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/86.0.4240.75+Safari/537.36 https://example.com/setup 304 0 0 27
    2020-10-12 16:04:13 192.168.2.11 GET /scripts/tinymce.min.js X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=343bfaca-427e-47a1-a168-b4250f62fc0e&SERVER-STATUS=304 443 - SOMEHOST.net Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/86.0.4240.75+Safari/537.36 https://example.com/setup 304 0 0 27
    2020-10-12 16:04:13 192.168.2.11 GET /socket.io/ session=ne19sc1orug1dsk7ndn1u4i7&EIO=3&transport=websocket&X-ARR-CACHE-HIT=0&X-ARR-LOG-ID=7afb0d81-b323-4e94-8ae7-c1a90bc2ef1c&SERVER-STATUS=101 443 - SOMEHOST.net Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/86.0.4240.75+Safari/537.36 - 502 5 12152 53
0

So apparently IIS cannot handle the permessage-deflate extension for the Sec-WebSocket-Extensions header. The solution is to clear the header as that is the only extension used by FoundryVTT:

<serverVariables>
  <set name="HTTP_SEC_WEBSOCKET_EXTENSIONS" value="" />
</serverVariables>

Do not forget to add HTTP_SEC_WEBSOCKET_EXTENSIONS as an allowed server variable for your site.

Web Socket Reverse, Web Socket Reverse 2 and ReverseProxyOutboundRule1 are not required to make FoundryVTT work with IIS.

Not the answer you're looking for? Browse other questions tagged or ask your own question.