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 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="[]|{}^\`"<>"
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="[]|{}^\`"<>"
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="[]|{}^\`"<>"
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 "%m %U%{sanitized.query}r %H" %s %b %D "%{sanitized.referer}r" "%{User-Agent}i" "%{jira.request.assession.id}r""/>
</Engine>
</Service>
</Server>
AI breakdown of the Failed Request Trace:
Failed Request Summary:
https://example.com:443/jira/
Event Breakdown:
GENERAL_REQUEST_START (2025-05-16T14:07:47.892Z)
https://example.com:443/jira/
GENERAL_ENDPOINT_INFORMATION (2025-05-16T14:07:47.892Z)
GENERAL_REQUEST_HEADERS (2025-05-16T14:07:47.892Z)
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
HANDLER_PRECONDITION_NOT_MATCH (Multiple Events - 2025-05-16T14:07:47.908Z)
GENERAL_GET_URL_METADATA (2025-05-16T14:07:47.908Z)
MODULE_PRECONDITION_NOT_MATCH (Multiple Events - 2025-05-16T14:07:47.908Z)
HANDLER_PRECONDITION_NOT_MATCH
events, this shows IIS cycling through potential modules, but none of them match the request.HANDLER_CHANGED (2025-05-16T14:07:47.908Z)
StaticFile
handler to process the request. This handler attempts to serve a static file from the file system.URL_REWRITE_START (2025-05-16T14:07:47.908Z)
/jira/
RULE_EVALUATION_START (2025-05-16T14:07:47.908Z)
(.*)
/jira/
PATTERN_MATCH (2025-05-16T14:07:47.908Z)
(.*)
REWRITE_ACTION (2025-05-16T14:07:47.908Z)
http://152.10.132.68:8080/jira/{R:1}
http://152.10.132.68:8080/jira/
RULE_EVALUATION_END (2025-05-16T14:07:47.908Z)
GENERAL_SET_REQUEST_HEADER (2025-05-16T14:07:47.908Z)
/jira/
URL_CHANGED (2025-05-16T14:07:47.908Z)
/jira/
http://152.10.132.68:8080/jira/
URL_REWRITE_END (2025-05-16T14:07:47.908Z)
http://152.10.132.68:8080/jira/
USER_SET (2025-05-16T14:07:47.908Z)
GENERAL_SET_RESPONSE_HEADER (Multiple Events - 2025-05-16T14:07:47.908Z)
Persistent-Auth
, X-Powered-By
, Strict-Transport-Security
).GENERAL_SEND_CUSTOM_ERROR (2025-05-16T14:07:47.908Z)
GENERAL_RESPONSE_ENTITY_BUFFER (2025-05-16T14:07:47.908Z)
GENERAL_FLUSH_RESPONSE_END (2025-05-16T14:07:47.908Z)
GENERAL_REQUEST_END (2025-05-16T14:07:47.908Z)
Analysis:
StaticFile
handler is selected, IIS tries to find a static file or directory named /jira/
on the server's file system, and it doesn't exist, resulting in the 404.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>
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.