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:
- URL:
https://example.com:443/jira/ - Site ID: 1
- App Pool ID: jira
- Process ID: 27876
- Verb: GET
- User: CLOUD51\CodeMonster
- Authentication: Negotiate
- Activity ID: {800004C9-003E-FA00-B63F-84710C7967BB}
- Failure Reason: STATUS_CODE
- Status Code: 404.4 (Not Found - No Handler Configured)
- Time Taken: 16 ms
Event Breakdown:
GENERAL_REQUEST_START (2025-05-16T14:07:47.892Z)
- Description: Request started.
- Details:
- Request URL:
https://example.com:443/jira/ - Request Verb: GET
- Request URL:
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
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
- Headers:
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.
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
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_MATCHevents, this shows IIS cycling through potential modules, but none of them match the request.
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
StaticFilehandler 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)
- Description: URL Rewrite Module started processing.
- Details:
- Request URL:
/jira/ - Scope: Distributed
- Type: Inbound
- Request URL:
RULE_EVALUATION_START (2025-05-16T14:07:47.908Z)
- Description: Evaluation of rewrite rule started.
- Details:
- Rule Name: ReverseProxyInboundRule1
- Pattern:
(.*) - StopProcessing: true
- RelativePath:
/jira/
PATTERN_MATCH (2025-05-16T14:07:47.908Z)
- Description: Pattern matched successfully.
- Details:
- Pattern:
(.*) - Input: (Empty)
- Matched: true
- Pattern:
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
- Substitution:
RULE_EVALUATION_END (2025-05-16T14:07:47.908Z)
- Description: Evaluation of rewrite rule ended.
- Details:
- Rule Name: ReverseProxyInboundRule1
- Succeeded: true
- StopProcessing: true
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
URL_CHANGED (2025-05-16T14:07:47.908Z)
- Description: URL changed.
- Details:
- Old URL:
/jira/ - New URL:
http://152.10.132.68:8080/jira/
- Old URL:
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/
- Request URL:
USER_SET (2025-05-16T14:07:47.908Z)
- Description: User information set.
- Details:
- AuthType: Negotiate
- UserName: CLOUD51\CodeMonster
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).
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
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.
GENERAL_FLUSH_RESPONSE_END (2025-05-16T14:07:47.908Z)
- Description: Response flushed.
- Details:
- BytesSent: 1502
- ErrorCode: 0 (The operation completed successfully.)
GENERAL_REQUEST_END (2025-05-16T14:07:47.908Z)
- Description: Request ended.
- Details:
- BytesSent: 1502
- BytesReceived: 717
- HttpStatus: 404
- HttpSubStatus: 4
Analysis:
- The request is coming in correctly.
- Authentication is working.
- The rewrite rule is executing successfully, but after IIS has already decided to use the StaticFile handler. This is the critical point.
- Because the
StaticFilehandler 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>
