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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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