tomcatiisjirareverse-proxy

IIS Reverse Proxy Won't Route to Rewritten Address - Loads Static File Instead


IIS Reverse Proxy Won't Route to Rewritten Address - Loads Static File Instead

I have successfully installed Jira and it is listening and successfully responding at http://152.10.132.68:8080/jira. The problem comes when I attempt to access Jira through a reverse proxy I've set up in IIS. Instead of navigating to Jira after rewriting the address, it seems IIS attempts to access the static content of the folder connected to the website but fails with a 404 error.

I've tried adjusting just about every setting in IIS, but to no avail. The funny thing is, I have an identical test server and the reverse proxy loads Jira just fine there.

The jira website is under the Default Web Site along with many other web sites.

I've set up Failed Request Tracing and tried to include one of the traces below but due to length, I ran it through AI to simplify it.

IIS Reverse Proxy Settings: IIS Reverse Proxy Settings

IIS web.config:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <caching enabled="false" enableKernelCache="false" />
        <directoryBrowse enabled="false" />
        <security>
            <requestFiltering allowDoubleEscaping="true" allowHighBitCharacters="true" />
            <access sslFlags="Ssl, SslNegotiateCert, SslRequireCert" />
            <authorization>
                <remove users="*" roles="" verbs="" />
                <add accessType="Allow" users="*" />
            </authorization>
        </security>
        <rewrite>
            <rules useOriginalURLEncoding="true">
                <rule name="ReverseProxyInboundRule1" enabled="true" stopProcessing="true">
                    <match url="(.*)" />
                    <action type="Rewrite" url="http://152.10.132.68:8080/jira/{R:1}" appendQueryString="true" />
                </rule>
            </rules>
        </rewrite>
        <defaultDocument enabled="true" />
        <httpProtocol>
            <customHeaders>
                <remove name="Strict-Transport-Security" />
                <remove name="X-Powered-By" />
                <add name="Strict-Transport-Security" value="max-age=15552001; includeSubDomains; preload" />
                <add name="X-Powered-By" value="ASP.NET" />
            </customHeaders>
        </httpProtocol>
        <urlCompression doStaticCompression="false" />
    </system.webServer>
    <system.web>
        <httpRuntime enableVersionHeader="true" />
    </system.web>
</configuration>

Tomcat Jira Server Config:

<?xml version="1.0" encoding="utf-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->
<Server port="5005" shutdown="SHUTDOWN">
    <Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
    <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on"/>
    <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
    <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
    <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>

    <Service name="Catalina">
        <!--
         ==============================================================================================================
         DEFAULT - Direct connector with no proxy for unproxied access to Jira.

         If using a http/https proxy, comment out this connector.
         ==============================================================================================================
        -->

        <!-- Relaxing chars because of JRASERVER-67974 -->
    <!--
        <Connector port="8080" relaxedPathChars="[]|" relaxedQueryChars="[]|{}^&#x5c;&#x60;&quot;&lt;&gt;"
                   maxThreads="150" minSpareThreads="25" connectionTimeout="20000" enableLookups="false"
                   maxHttpHeaderSize="8192" protocol="HTTP/1.1" useBodyEncodingForURI="true" redirectPort="8443"
                   acceptCount="100" disableUploadTimeout="true" bindOnInit="false"/>
    -->

        <!--
         ==============================================================================================================
         HTTP - Proxying Jira via Apache or Nginx over HTTP

         If you're proxying traffic to Jira over HTTP, uncomment the below connector and comment out the others.
         Ensure the proxyName and proxyPort are updated with the appropriate information if necessary as per the docs.

         See the following for more information:

            Apache - https://confluence.atlassian.com/x/4xQLM
            nginx  - https://confluence.atlassian.com/x/DAFmGQ
         ==============================================================================================================
        -->

        <!--
        <Connector port="5050" relaxedPathChars="[]|" relaxedQueryChars="[]|{}^&#x5c;&#x60;&quot;&lt;&gt;"
                   maxThreads="150" minSpareThreads="25" connectionTimeout="20000" enableLookups="false"
                   maxHttpHeaderSize="8192" protocol="HTTP/1.1" useBodyEncodingForURI="true" redirectPort="8443"
                   acceptCount="100" disableUploadTimeout="true" bindOnInit="false" scheme="http"
                   proxyName="<subdomain>.<domain>.com" proxyPort="80"/>

        -->
        <!--
         ==============================================================================================================
         HTTPS - Proxying Jira via Apache or Nginx over HTTPS

         If you're proxying traffic to Jira over HTTPS, uncomment the below connector and comment out the others.
         Ensure the proxyName and proxyPort are updated with the appropriate information if necessary as per the docs.

         See the following for more information:

            Apache - https://confluence.atlassian.com/x/PTT3MQ
            nginx  - https://confluence.atlassian.com/x/DAFmGQ
         ==============================================================================================================
        -->

        
        <Connector port="8080" relaxedPathChars="[]|" relaxedQueryChars="[]|{}^&#x5c;&#x60;&quot;&lt;&gt;"
                   maxThreads="150" minSpareThreads="25" connectionTimeout="20000" enableLookups="false"
                   maxHttpHeaderSize="8192" protocol="HTTP/1.1" useBodyEncodingForURI="true" redirectPort="8443"
                   acceptCount="100" disableUploadTimeout="true" bindOnInit="false" secure="true" scheme="https"
                   proxyName="example.com" proxyPort="443"/>
        

        <!--
         ==============================================================================================================
         AJP - Proxying Jira via Apache over HTTP or HTTPS

         If you're proxying traffic to Jira using the AJP protocol, uncomment the following connector line
         See the following for more information:

            Apache - https://confluence.atlassian.com/x/QiJ9MQ
         ==============================================================================================================
        -->

        <!--
        <Connector port="8009" URIEncoding="UTF-8" enableLookups="false" protocol="AJP/1.3"/>
        -->

        <Engine name="Catalina" defaultHost="localhost">
            <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">

                <Context path="/jira" docBase="${catalina.home}/atlassian-jira" reloadable="false" useHttpOnly="true">
                    <Resource name="UserTransaction" auth="Container" type="javax.transaction.UserTransaction"
                              factory="org.objectweb.jotm.UserTransactionFactory" jotm.timeout="60"/>
                    <Manager pathname=""/>
                    <JarScanner scanManifest="false"/>
                    <Valve className="org.apache.catalina.valves.StuckThreadDetectionValve" threshold="120" />
                    <Parameter name="jira.home" value="D:\jira\home" />
                </Context>

            </Host>
            <Valve className="org.apache.catalina.valves.AccessLogValve"
                   pattern="%a %{jira.request.id}r %{jira.request.username}r %t &quot;%m %U%{sanitized.query}r %H&quot; %s %b %D &quot;%{sanitized.referer}r&quot; &quot;%{User-Agent}i&quot; &quot;%{jira.request.assession.id}r&quot;"/>
        </Engine>
    </Service>
</Server>

AI breakdown of the Failed Request Trace:

Failed Request Summary:

Event Breakdown:

  1. GENERAL_REQUEST_START (2025-05-16T14:07:47.892Z)

    • Description: Request started.
    • Details:
      • Request URL: https://example.com:443/jira/
      • Request Verb: GET
  2. GENERAL_ENDPOINT_INFORMATION (2025-05-16T14:07:47.892Z)

    • Description: Endpoint information.
    • Details:
      • Remote Address: 152.10.200.15
      • Remote Port: 56647
      • Local Address: 152.10.132.68
      • Local Port: 443
  3. GENERAL_REQUEST_HEADERS (2025-05-16T14:07:47.892Z)

    • Description: Request headers received.
    • Details:
      • Headers:
        Cache-Control: max-age=0
        Connection: keep-alive
        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
        Accept-Encoding: gzip, deflate, br, zstd
        Accept-Language: en-US,en;q=0.9
        Host: example.com
        User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0
        sec-ch-ua: "Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"
        sec-ch-ua-mobile: ?0
        sec-ch-ua-platform: "Windows"
        Upgrade-Insecure-Requests: 1
        Sec-Fetch-Site: none
        Sec-Fetch-Mode: navigate
        Sec-Fetch-User: ?1
        Sec-Fetch-Dest: document
        
  4. HANDLER_PRECONDITION_NOT_MATCH (Multiple Events - 2025-05-16T14:07:47.908Z)

    • Description: Various handlers' preconditions were not met.
    • Details: These events show IIS cycling through potential handlers, but none of them match the request based on their preconditions. This is normal behavior as IIS tries to find the appropriate handler. The names of the handlers and the specific preconditions are listed in each event.
  5. GENERAL_GET_URL_METADATA (2025-05-16T14:07:47.908Z)

    • Description: URL metadata retrieved.
    • Details:
      • Physical Path: (Empty - This is a key indicator!)
      • AccessPerms: 617
  6. MODULE_PRECONDITION_NOT_MATCH (Multiple Events - 2025-05-16T14:07:47.908Z)

    • Description: Various modules' preconditions were not met.
    • Details: Similar to the HANDLER_PRECONDITION_NOT_MATCH events, this shows IIS cycling through potential modules, but none of them match the request.
  7. HANDLER_CHANGED (2025-05-16T14:07:47.908Z)

    • Description: Handler changed.
    • Details:
      • Old Handler Name: (Empty)
      • New Handler Name: StaticFile
      • New Handler Modules: StaticFileModule, DefaultDocumentModule, DirectoryListingModule
      • This is the crucial event: IIS has selected the StaticFile handler to process the request. This handler attempts to serve a static file from the file system.
  8. URL_REWRITE_START (2025-05-16T14:07:47.908Z)

    • Description: URL Rewrite Module started processing.
    • Details:
      • Request URL: /jira/
      • Scope: Distributed
      • Type: Inbound
  9. RULE_EVALUATION_START (2025-05-16T14:07:47.908Z)

    • Description: Evaluation of rewrite rule started.
    • Details:
      • Rule Name: ReverseProxyInboundRule1
      • Pattern: (.*)
      • StopProcessing: true
      • RelativePath: /jira/
  10. PATTERN_MATCH (2025-05-16T14:07:47.908Z)

    • Description: Pattern matched successfully.
    • Details:
      • Pattern: (.*)
      • Input: (Empty)
      • Matched: true
  11. REWRITE_ACTION (2025-05-16T14:07:47.908Z)

    • Description: URL Rewrite action performed.
    • Details:
      • Substitution: http://152.10.132.68:8080/jira/{R:1}
      • RewriteURL: http://152.10.132.68:8080/jira/
      • AppendQueryString: true
  12. RULE_EVALUATION_END (2025-05-16T14:07:47.908Z)

    • Description: Evaluation of rewrite rule ended.
    • Details:
      • Rule Name: ReverseProxyInboundRule1
      • Succeeded: true
      • StopProcessing: true
  13. GENERAL_SET_REQUEST_HEADER (2025-05-16T14:07:47.908Z)

    • Description: Request header set.
    • Details:
      • Header Name: X-Original-URL
      • Header Value: /jira/
      • Replace: true
  14. URL_CHANGED (2025-05-16T14:07:47.908Z)

    • Description: URL changed.
    • Details:
      • Old URL: /jira/
      • New URL: http://152.10.132.68:8080/jira/
  15. URL_REWRITE_END (2025-05-16T14:07:47.908Z)

    • Description: URL Rewrite Module ended processing.
    • Details:
      • Request URL: http://152.10.132.68:8080/jira/
  16. USER_SET (2025-05-16T14:07:47.908Z)

    • Description: User information set.
    • Details:
      • AuthType: Negotiate
      • UserName: CLOUD51\CodeMonster
  17. GENERAL_SET_RESPONSE_HEADER (Multiple Events - 2025-05-16T14:07:47.908Z)

    • Description: Response headers set.
    • Details: These events list the response headers being set by IIS (e.g., Persistent-Auth, X-Powered-By, Strict-Transport-Security).
  18. GENERAL_SEND_CUSTOM_ERROR (2025-05-16T14:07:47.908Z)

    • Description: Custom error sent.
    • Details:
      • HttpStatus: 404
      • HttpSubStatus: 4 (No Handler Configured)
      • FileNameOrURL: 404.htm
  19. GENERAL_RESPONSE_ENTITY_BUFFER (2025-05-16T14:07:47.908Z)

    • Description: Response entity buffer.
    • Details: This contains the HTML content of the 404 error page.
  20. GENERAL_FLUSH_RESPONSE_END (2025-05-16T14:07:47.908Z)

    • Description: Response flushed.
    • Details:
      • BytesSent: 1502
      • ErrorCode: 0 (The operation completed successfully.)
  21. GENERAL_REQUEST_END (2025-05-16T14:07:47.908Z)

    • Description: Request ended.
    • Details:
      • BytesSent: 1502
      • BytesReceived: 717
      • HttpStatus: 404
      • HttpSubStatus: 4

Analysis:

Recommendations (Repeating the Key Solution):

You must prevent the StaticFile handler from being selected in the first place. The best way to do this is to make your rewrite rule more specific and ensure it has stopProcessing="true". Use the following rule:

<rewrite>
  <rules>
    <rule name="Reverse Proxy to Jira" stopProcessing="true">
      <match url="^jira(/)?$" />  <!-- Matches /jira or /jira/ exactly -->
      <action type="Rewrite" url="http://152.10.132.68:8080/jira/{R:1}" />
      <serverVariables>
        <set name="HTTP_X_FORWARDED_PROTO" value="https" />
      </serverVariables>
    </rule>
  </rules>
</rewrite>

Solution

  • After a solid month of investigation into this issue, I've found the answer! The Application Request Routing (ARR) was not properly installed on the server. ARR was installed and I could configure it in IIS as suggested in the Jira documentation, but IIS didn't actually do the routing.

    I uninstalled ARR and reinstalled it and the URL Rewrite works perfectly.