ObjectiveOur 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
RequirementsPrivileged 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
IntroductionUsing 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 softwareFirst 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.
We install the
# yum install httpd tomcat tomcat-webapps
tomcat-webappsfor 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
And our webserver:
# systemctl start tomcat
# systemctl enable httpd
# systemctl start httpd
httpdinstallation contains the proxy modules we need. To check that it is so, we can query the webserver with
Note: 1.x Apache versions use
# apachectl -M | grep ajp proxy_ajp_module (shared)
mod_jkmodule instead of
httpd configurationThe 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/examplesto be served by the
examplesweb 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.don Red Hat flavors, with the extension of
.conf. Our setup doesn't need Tomcat to be reachable directly, so we use
localhostas target host in the
To be on the safe side, we can verify that our configuration is correct before applying with
<VirtualHost ws.foobar.com:80> ServerName ws.foobar.com ProxyRequests Off ProxyPass /examples ajp://localhost:8009/examples ProxyPassReverse /examples ajp://localhost:8009/examples </VirtualHost>
If the configuration test returns an error like the following:
# apachectl configtest Syntax OK
If means that our
Could not resolve host name ws.foobar.com -- ignoring!
ServerNamedirective 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/hostsfile 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
Tomcat configurationWith 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:
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:
# 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" /> [..]
To apply we can restart Tomcat with:
Connector address="127.0.0.1" port=..."
In our lab machine will not do this, as we need to see that we are served the same content on both port
# systemctl restart tomcat
TestingOur minimal AJP proxy setup is complete, we can test it. From the command line we can call the
examplesapplication directly on port
And see the contents provided:
$ 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 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:
$ 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>
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:
$ 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]
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.
$ tail examples.1 <h3>Apache Tomcat Examples</h3> [...]
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,
httpdcan serve any other content. We can create static content that is reachable on some other URL on the same server:
By pointing our browser to this new resource, we are provided with the new static content.
# mkdir /var/www/html/static_content # echo "<html><body>Static content</body></html>" > /var/www/html/static_content/static.html
If the Tomcat container would not be reachable, we would not know the answer coming somewhere other than the webserver. As we only proxied only a specific application, the container's default ROOT application is not reachable trough the proxy, thus hidden from everything beyond the webserver.
ConclusionThe 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.