Tomcat behind a Reverse-Proxy

Oli Zimpasser
5 min readJan 8, 2023

As long as you offer only http and do simple host based routing on the Reverse-Proxy your application doesn't have to do anything to support behing behind a Reverse-Proxy. Having said that you still might find it odd to see only the Reverse-Proxy’s IP in your Tomcat's access log files.

As soon as you use https or a different path prefix on the Reverse-Proxy, your application might run into problems.

Let’s look at those problems and their solutions.

The possible Problems

Your application sees the Reverse-Proxy's IP as the Source IP

As the TCP connections are terminated on the Reverse-Proxy and traffic is forwarded on a second TCP connection you only see the Reverse-Proxy's IP as the "Remote IP" in Tomcat. This is also seen in access logs or anywhere in your application where you use HttpServletRequest.getRemoteAddr() .

Your application might be deployed under a different URL path prefix

You might have developed your application — and thus always deployed it — as a myapp.war, so the context path — the prefix on the URL path — was /myapp/. Maybe your application is now running on a Reverse-Proxy, configured by an administrator who wants to run your application on a different path prefix or just without any in case of a host based routing scenario.

You see Http protocol is "http" even the user accesses your system via https

If your application uses HttpServletRequest.isSecure() you will notice that this value is set to what the Reverse-Proxy is using, not what the client is actually asking for.

Fixing the Source IP

Assuming that your Reverse-Proxy is setting the http header X-Forwarded-For and X-Forwarded-Proto, Tomcat provides a very elegant and simple solution to replace all relevant TCP level information in a HttpServletRequest with this configuration:

<!-- inside conf/server.xml -->

<Host ...>
<Valve className="org.apache.catalina.valves.RemoteIpValve" />
</Host>

See here for the full documentation to configure the validation for trusted Reverse-Proxy IPs and different http/https server ports. By default, 10/8, 192.168/16, 169.254/16, 127/8, 172.16/12, and ::1 are allowed as Reverse-Proxy IPs.

Fixing Access Logs

There is another configuration change needed to let Tomcat use the X-Forwarded-For header information for the access logs:

<!-- inside conf/server.xml -->

<Host ...>
<Valve className="org.apache.catalina.valves.RemoteIpValve" httpServerPort="8080" httpsServerPort="8443" />

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="combined" requestAttributesEnabled="true" />
</Host>

The attribute requestAttributesEnabled=”true” tells Tomcat to use a possible X-Forwarded-For for the access logs.

As a side note pattern=”combined” switches Tomcat's access logs to the combined log format known from Apache.

Context Path configuration

At this point it is worth to mention that your application must not hardcode URL path prefix nor is it necessary to make it configurable in your application.

Whenever you construct an absolute URL you have to call HttpServletRequest.getContextPath() for the URL prefix.

Then you can freely configure the context path via <Context>. This can be done in two ways:

Inside server.xml

To define a different context path you can simple add a <Context> element inside <Host> of server.xml.

<Host appBase="webapps" ...>
<Context docBase="path/to/myapp" path="app" />
</Host>

Leave path empty to use the / path. docBase can be relative to appBase or absolute. Keep in mind that if you don’t want to load your application twice you have to move the docBase outside of appBase.

As the documentation says:

It is NOT recommended to place <Context> elements directly in the server.xml file.

As a XML file in conf/Catalina/localhost

So the recommended way is to put a file into conf/Catalina/localhost (replace localhost in case of virtual hosting with your domain) and here the filename of the XML file will define the path prefix.

Use ROOT.xml if you want the / path, for /myapp use myapp.xml, like this:

<Context 
docBase="/path/to/myapp"
/>

As a side note: while you can have <Context> inside your applications directorie’s META-INF/Context.xml file, this cannot define a different path.

Examples of wrongly and properly configures systems

Let’s assume we are running a HAproxy Reverse-Proxy on port 8000/8443. All trafic is forwarded via http only to a Tomcat running on 8080. where we have deployed an applicaton into webapps/myapp

Let’s also accept that we are not redirecting http to https for this analysis.

Let’s check what Tomcat sees without any configuration.

We have configured HAProxy to add several http headers defining what has happened, but Tomcat and its HttpServletRequest API is not taking this account and thus shows wrong data for Remote Address, Remote Host in case of this http request.

For an https request even isSecure() is wrong.

Let’s fix this by adding

<Valve className="org.apache.catalina.valves.RemoteIpValve" 
httpServerPort="8000" httpsServerPort="8443" />

to conf/server.xml.

We are see:

That all information retrieved from Tomcat’s HttpServletRequest API is equal to the client side’s point of view.

Even for an https request, the isSecure() shows true.

Tomcat logs

After adding requestAttributesEnabled="true" to the logger definition in server.xml you also see the right Source IP in the access logs:

Download all files for your own tests

I have uploaded a project with HAProxy, Tomcat and all files you need to reproduce the experiments shown in this article.

https://github.com/oglimmer/tomcat-behind-reverse-proxy

--

--