Our objective is to setup Apache httpd to work as a proxy in front of the Apache Tomcat application container.
Operating System and Software Versions
- Operating system: Red Hat Enterprise Linux 7.5
- Software: Apache httpd, Apache Tomcat
Privileged access to the system
- # – requires given linux commands to be executed with root privileges either directly as a root user or by use of
- $ – given linux commands to be executed as a regular non-privileged user
Using Apache httpd as a proxy to an Apache Tomcat application container is a common setup. It comes with many use cases, the most trivial is serving static content from
httpd, while providing services implementing heavy business logic from an application written in Java that resides in the Tomcat container.
By creating a proxy, we can create a kind of front-end to the application layer, where we can introduce security measures in the webserver, apply load balancing, use conditional redirect, or use any other functionality provided by the webserver. This way we don’t need to implement any of these features in our application, and can focus it’s capabilities to the service itself. We will have a full-featured webserver presented for the users, some of the urls forwarded silently to the application container that may be not accessible by itself. The application’s answers are forwarded back to the clients who will not know that they spoke anything else but the webserver – that is, if we take care not exposing any information (like unhandled error messages) from the application that can make them guess there are more than one layers.
We will use the AJP protocol that can be used between webservers and Java based application containers to provide the ability to balance the load between multiple application servers – however, to set up a load balancer is out of the scope of this tutorial.
We will configure our setup on Red Hat Linux 7.5, but the Apache webserver, the AJP module and the Apache Tomcat application container are available everywhere, and thus this setup is portable with small adjustments like filesystem paths or service names.
Installing required software
First we need to install the services we will use. In a load balanced setup Tomcat server(s) could be on different machines, and often they are, providing a farm of containers that build up a service.
# yum install httpd tomcat tomcat-webapps
We install the
tomcat-webapps for testing purposes, within this package is an examples web application deployed into our Tomcat server on installation. We’ll use this application to test that our setup is working as intended.
Now we can enable and start our Tomcat server:
# systemctl enable tomcat
# systemctl start tomcat
And our webserver:
# systemctl enable httpd
# systemctl start httpd
httpd installation contains the proxy modules we need. To check that it is so, we can query the webserver with
# apachectl -M | grep ajp proxy_ajp_module (shared)
Note: 1.x Apache versions use
mod_jk module instead of
The examples web application deployed into Tomcat are published after installation by default on
server-url:8080/examples. We will proxy requests coming to the server’s port 80 (the default http port) requesting something from the
server-url/examples to be served by the
examples web application deployed into Tomcat. Requests coming to any other URL on the server will be served by the web server. We’ll set up some static content to show this functionality.
In our example the server is called
ws.foobar.com. For the proxy to work create a text file with your favorite editor under the webserver’s drop-in configuration directory, which is
/etc/httpd/conf.d on Red Hat flavors, with the extension of
.conf. Our setup doesn’t need Tomcat to be reachable directly, so we use
localhost as target host in the
<VirtualHost ws.foobar.com:80> ServerName ws.foobar.com ProxyRequests Off ProxyPass /examples ajp://localhost:8009/examples ProxyPassReverse /examples ajp://localhost:8009/examples </VirtualHost>
To be on the safe side, we can verify that our configuration is correct before applying with
# apachectl configtest Syntax OK
If the configuration test returns an error like the following:
Could not resolve host name ws.foobar.com -- ignoring!
If means that our
ServerName directive is invalid, as it can’t be resolved by the webserver. Either we need to register it in the (local or global) DNS, or provide a line in the
/etc/hosts file that contains the host’s public IP address followed by the name we gave in the above configuration. If the hosts file already contains the IP with another name (maybe the real hostname), we can add the servername after the host’s name(s) in the same line, the setup will work.
After successful test we need to apply the new configuration by restarting the webserver:
# systemctl restart httpd
With the default install the Tomcat container will listen to AJP requests on all interfaces on port 8009. This can be verified in the main configuration file:
# view /usr/share/tomcat/conf/server.xml [..] <!-- Define an AJP 1.3 Connector on port 8009 --> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> [..]
If we don’t need the Tomcat container and the applications within to be reachable by themselves, we can set every connector to listen only on localhost:
Connector address="127.0.0.1" port=..."
To apply we can restart Tomcat with:
# systemctl restart tomcat
In our lab machine will not do this, as we need to see that we are served the same content on both port
Our minimal AJP proxy setup is complete, we can test it. From the command line we can call the
examples application directly on port
$ wget http://ws.foobar.com:8080/examples --2018-09-13 11:00:58-- http://ws.foobar.com:8080/examples Resolving ws.foobar.com (ws.foobar.com)... 10.104.1.165 Connecting to ws.foobar.com (ws.foobar.com)|10.104.1.165|:8080... connected. HTTP request sent, awaiting response... 302 Found Location: /examples/ [following] --2018-09-13 11:00:58-- http://ws.foobar.com:8080/examples/ Reusing existing connection to ws.foobar.com:8080. HTTP request sent, awaiting response... 200 OK Length: 1253 (1.2K) [text/html] Saving to: 'examples' 100%[=========================================================================================================================================================================>] 1,253 --.-K/s in 0s 2018-09-13 11:00:58 (102 MB/s) - 'examples' saved [1253/1253]
And see the contents provided:
$ tail examples <h3>Apache Tomcat Examples</H3> <p></p> <ul> <li><a href="servlets">Servlets examples</a></li> <li><a href="jsp">JSP Examples</a></li> <li><a href="websocket/index.xhtml">WebSocket (JSR356) Examples</a></li> <li><a href="websocket-deprecated">WebSocket Examples using the deprecated Apache Tomcat proprietary API</a></li> </ul> </body></html>
And if we call the same application trough our AJP proxy, we should also get an answer, while there isn’t any content in the webserver’s document root:
$ wget http://ws.foobar.com/examples --2018-09-13 11:01:09-- http://ws.foobar.com/examples Resolving ws.foobar.com (ws.foobar.com)... 10.104.1.165 Connecting to ws.foobar.com (ws.foobar.com)|10.104.1.165|:80... connected. HTTP request sent, awaiting response... 302 Found Location: /examples/ [following] --2018-09-13 11:01:09-- http://ws.foobar.com/examples/ Reusing existing connection to ws.foobar.com:80. HTTP request sent, awaiting response... 200 OK Length: 1253 (1.2K) [text/html] Saving to: 'examples.1' 100%[=========================================================================================================================================================================>] 1,253 --.-K/s in 0s 2018-09-13 11:01:09 (101 MB/s) - 'examples.1' saved [1253/1253]
If all works, we will get an answer with the same contents, as the final answer is provided by the same application within the container:
$ tail examples.1 <h3>Apache Tomcat Examples</h3> [...]
We can also test our setup with a browser. We need to call all URLs with the server’s name as the host (at least the one that is proxied). For that the machine running the browser need to be able to resolve the server name, by means of DNS or hosts file.
In our lab environment we haven’t disabled Tomcat listening on the public interface, so we can see what is provided when asked directly on port
We can get the same content trough the AJP proxy provided by the webserver on port
While acting as a proxy,
httpd can serve any other content. We can create static content that is reachable on some other URL on the same server:
# mkdir /var/www/html/static_content # echo "<html><body>Static content</body></html>" > /var/www/html/static_content/static.html
By pointing our browser to this new resource, we are provided with the new static content.
If the Tomcat container would not be reachable, we would not know the answer coming somewhere other than the webserver. As we proxied only a specific application, the container’s default ROOT application is not reachable trough the proxy, thus hidden from everything beyond the webserver.
The Apache webserver is highly extendable by the means of modules, one of them is the AJP proxy module. The above guide uses one machine and exposes one application with the proxy, but the same webserver could provide a single entry to many applications, possibly on many hosts running application containers, while providing other web content as well.
Combined with other modules, like
mod_security, we can add many features to our service without the need to develop them within the application, or if the need arises, redirect the proxy to another endpoint with a single edition of the configuration file and the reload of the webserver, making a migration or the introduction of the application’s new release a matter of seconds. The same reload can lead the visitor to a page explaining planned downtime, while maintenance is performed on the application servers – the use cases of an AJP proxy are only limited by the imagination of the IT staff.