DEV Community

Jesús G. Calderín
Jesús G. Calderín

Posted on

Set up a reverse proxy in Linux

Prerequisites

1) Install and configure a Tomcat cluster, and set the following environment variables in ~/.bashrc:

$TOMCAT1_HOME=<your tomcat1 home folder>
$TOMCAT2_HOME=<your tomcat2 home folder>

2) Verify Linux's builtin apache is installed:

$ sudo systemctl status apache2
● apache2.service - The Apache HTTP Server
     Loaded: loaded (/lib/systemd/system/apache2.service; 
     enabled; vendor preset: enabled)
     Active: active (running) since Sun 2025-09-21 12:43:12 CEST;
Enter fullscreen mode Exit fullscreen mode

Create a new website

Specify in .bashrc the home folder of our new website:

$ tail -n 3 ~/.bashrc
export TOMCAT1_HOME="/apps/myapp/clus1/tomcat1"
export TOMCAT2_HOME="/apps/myapp/clus1/tomcat2"
export MYSITE_HOME="/var/sites/mysite"
$ source ~/.bashrc
Enter fullscreen mode Exit fullscreen mode

, and also set it in /etc/apache2/envvars:

$ tail -n 1 /etc/apache2/envvars
export MYSITE_HOME=/var/sites/mysite
Enter fullscreen mode Exit fullscreen mode

Create our new website's conf and www/html folders:

$ sudo mkdir -p $MYSITE_HOME/conf
$ sudo mkdir -p $MYSITE_HOME/www/html
Enter fullscreen mode Exit fullscreen mode

Create an index.html inside $MYSITE_HOME/www/html:

$ cat $MYSITE_HOME/www/html/index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>MySite Default Page: It works</title>
  </head>
  <body>
        Welcome to MySite
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Create a virtual host for our site

We copy the default conf from built-in apache:

$ sudo cp /etc/apache2/sites-available/000-default.conf \
$MYSITE_HOME/conf/localhost.conf
Enter fullscreen mode Exit fullscreen mode

, and add the following lines:

$ cat $MYSITE_HOME/conf/localhost.conf
<VirtualHost *:80>
        ServerName localhost
        ServerAdmin webmaster@localhost

        DocumentRoot ${MYSITE_HOME}/www/html
        <Directory ${MYSITE_HOME}/www/>
          Options Indexes FollowSymLinks
          AllowOverride None
          Require all granted
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

</VirtualHost>
Enter fullscreen mode Exit fullscreen mode

Enable our site

We first make our site available:

$ sudo ln -s $MYSITE_HOME/conf/localhost.conf \
/etc/apache2/sites-available/local-localhost.conf
Enter fullscreen mode Exit fullscreen mode

, and then we enable it:

$ sudo a2ensite local-localhost
Enabling site local-localhost.
To activate the new configuration, you need to run:
  systemctl reload apache2
Enter fullscreen mode Exit fullscreen mode

Finally, stop and restart apache:

$ sudo systemctl stop apache2
$ sudo systemctl start apache2

, and test that our new site works:

$ curl http://localhost
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>MySite Default Page: It works</title>
  </head>
  <body>
        Welcome to MySite
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Set up mod_jk

There are two modules that I know for our apache web server to communicate with our backend Tomcat servers: mod_proxy and mod_jk.
I'm going to use the mod_jk plugin, which will communicate via the AJP protocol.

Enable AJP

First thing is to enable the AJP connectors in our Tomcats, which are disabled by default.
For that, we need to go to server.xml and uncomment the AJP connector element:

$ cat $TOMCAT1_HOME/conf/server.xml
    <!-- Define an AJP 1.3 Connector on port 8009 -->

    <Connector protocol="AJP/1.3"
               address="localhost"
               port="8009"
               redirectPort="8443"
               maxParameterCount="1000"
               secretRequired="false"
               />
Enter fullscreen mode Exit fullscreen mode

N.B:

  • I had to add secretRequired="false"
  • The defaul value in address is "::1", but that doesn't work for me, so I set it to "localhost", although "127.0.0.1" will also do.

Do the same for tomcat2 but don't forget to use a different port, cause both tomcats are on the same machine:

<!-- Define an AJP 1.3 Connector on port 9009 -->

    <Connector protocol="AJP/1.3"
               address="127.0.0.1"
               port="9009"
               redirectPort="8443"
               maxParameterCount="1000"
               secretRequired="false"
               />
Enter fullscreen mode Exit fullscreen mode

After restarting the tomcats, we should see both AJP ports in use:

$ netstat -nlp|grep -E "8009|9009"
tcp6       0      0 127.0.0.1:8009  :::*  LISTEN      60789/java
tcp6       0      0 127.0.0.1:9009  :::*  LISTEN      120768/java
Enter fullscreen mode Exit fullscreen mode

So our situation is that our examples application is available via our tomcats:

$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/examples/
200
$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:9080/examples/
200
Enter fullscreen mode Exit fullscreen mode

but not yet via our apache:

$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost/examples/
404
Enter fullscreen mode Exit fullscreen mode

That's what we will try to achieve with mod_jk: that requests for examples app are forwarded to the tomcats.

Configure and enable mod_jk

First we copy the default mod_jk conf:

$ sudo cp /etc/apache2/mods-available/jk.conf $MYSITE_HOME/conf/jk.conf
$ sudo cp /etc/apache2/mods-available/jk.load $MYSITE_HOME/conf/jk.load
Enter fullscreen mode Exit fullscreen mode

Now we create 2 files inside $MYSITE_HOME/conf:

  • workers.properties: will contain pointers to our tomcat AJP connectors and will define a load-balancer type worker.
  • uriworkermap.properties: will contain the mapping from /examples uri to our load-balancer.
$ cat $MYSITE_HOME/conf/workers.properties
worker.list=worker1, worker2, lb

# Set properties for worker1 (our tomcat1 server)
worker.worker1.type=ajp13
worker.worker1.host=localhost
worker.worker1.port=8009

# Set properties for worker2 (our tomcat2 server)
worker.worker2.type=ajp13
worker.worker2.host=localhost
worker.worker2.port=9009

worker.lb.type=lb
worker.lb.balance_workers=worker1, worker2

$ cat $MYSITE_HOME/conf/uriworkermap.properties
# Send everything for context /examples to load-balancer worker
/examples/*=lb
Enter fullscreen mode Exit fullscreen mode

Now we point to these 2 files in our jk.conf file:

$ cat $MYSITE_HOME/conf/jk.conf
...
JkWorkersFile ${MYSITE_HOME}/conf/workers.properties
...
JkMountFile ${MYSITE_HOME}/conf/uriworkermap.properties
Enter fullscreen mode Exit fullscreen mode

The configuration is finished.

Next, we make our module available:

$ sudo ln -s $MYSITE_HOME/conf/jk.conf \
/etc/apache2/mods-available/local-jk.conf
$ sudo ln -s $MYSITE_HOME/conf/jk.load \
/etc/apache2/mods-available/local-jk.load
Enter fullscreen mode Exit fullscreen mode

We now have to disable the default mod_jk cause only one JkWorkersFile is permitted:

$ sudo a2dismod jk

Next, enable our custom jk and reload:

$ sudo a2enmod local-jk
$ sudo systemctl reload apache2
Enter fullscreen mode Exit fullscreen mode

Now, the forwarding should work:

$ curl http://localhost/examples/
<!DOCTYPE HTML><html lang="en"><head>
<meta charset="UTF-8">
<title>Apache Tomcat Examples</title>
</head>
<body>
<p>
<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 Examples</a></li>
</ul>
</body></html>
Enter fullscreen mode Exit fullscreen mode

Next, we will test that load-balancing, sticky sessions and fail-over are all working as expected:

Testing load-balancing

To test that sessions are balanced, we can use servlet SessionExample in the examples app.

I have modified the following file

$TOMCAT_HOME/webapps/examples/WEB-INF/classes/LocalStrings.properties

in both tomcats to include the literal "1" and "2" respectively at the end of the following property:

sessions.title=Sessions Example Tomcat

, so I can easily identify to which tomcat requests are being forwarded.

Running different requests should bounce alternatively (or nearly) between tomcat1 and tomcat2:

$ curl http://localhost/examples/servlets/servlet/SessionExample|\
grep "<title>Sessions Example"
<title>Sessions Example Tomcat 1</title>
$ curl http://localhost/examples/servlets/servlet/SessionExample|\
grep "<title>Sessions Example"
<title>Sessions Example Tomcat 2</title>
$ curl http://localhost/examples/servlets/servlet/SessionExample|\
grep "<title>Sessions Example"
<title>Sessions Example Tomcat 1</title>
$ curl http://localhost/examples/servlets/servlet/SessionExample|\
grep "<title>Sessions Example"
<title>Sessions Example Tomcat 2</title>
Enter fullscreen mode Exit fullscreen mode

Testing sticky sessions

For sticky sessions to work, we have to set the jvmRoute attribute in the engine element in tomcat's server.xml to the name of the respective node from workers.properties file (worker1 and worker2 in our case):

<Engine jvmRoute="worker1" name="Catalina" ...>
<Engine jvmRoute="worker2" name="Catalina" ...>

$ grep -r --include="server.xml" "jvmRoute=" /apps/myapp/clus1
/tomcat2/conf/server.xml:    <Engine name="Catalina" defaultHost="localhost" jvmRoute="worker2">
/tomcat1/conf/server.xml:    <Engine name="Catalina" defaultHost="localhost" jvmRoute="worker1">
Enter fullscreen mode Exit fullscreen mode

To test that requests belonging to the same session "stick" to the same server, we can send the cookie JSESSIONID filled with the ID returned by the first request and check both that ID returned is the same and receiving tomcat too:

$ curl http://localhost/examples/servlets/servlet/SessionExample|\
grep "Session ID" -B 1
<h3>Sessions Example Tomcat 1</h3>
Session ID: 160ECEE2FE688B6649DC109F03EA1C7B.worker1

$ curl --cookie "JSESSIONID=160ECEE2FE688B6649DC109F03EA1C7B.worker1"\
http://localhost/examples/servlets/servlet/SessionExample|\
grep "Session ID" -B 1
<h3>Sessions Example Tomcat 1</h3>
Session ID: 160ECEE2FE688B6649DC109F03EA1C7B.worker1
Enter fullscreen mode Exit fullscreen mode

Testing fail-over

We will proceed with the following sequence

  • send a request
  • note which tomcat served it and which session ID is returned
  • stop that tomcat
  • send another request for the same session via JESSIONID cookie
  • verify that the other tomcat has served it and the JESSSIONID returned is the same
$ curl http://localhost/examples/servlets/servlet/SessionExample|\
grep "<h3>Sessions Example" -A 1
<h3>Sessions Example Tomcat 1</h3>
Session ID: 2D3B488194BBC78479370196455D4384.worker1

$ /apps/myapp/clus1/tomcat1/shutdown.sh
Using CATALINA_BASE:   /apps/myapp/clus1/tomcat1
Using CATALINA_HOME:   /opt/apache-tomcat-9.0.109
Using CATALINA_TMPDIR: /apps/myapp/clus1/tomcat1/temp
Using JRE_HOME:        /opt/jdk1.8.0_451
Using CLASSPATH:       /opt/apache-tomcat-9.0.109/bin/bootstrap.jar:
/opt/apache-tomcat-9.0.109/bin/tomcat-juli.jar
Using CATALINA_OPTS:

$ curl --cookie "JSESSIONID=2D3B488194BBC78479370196455D4384.worker1"\
http://localhost/examples/servlets/servlet/SessionExample|\
grep "<h3>Sessions Example" -A 1
<h3>Sessions Example Tomcat 2</h3>
Session ID: 2D3B488194BBC78479370196455D4384.worker2
Enter fullscreen mode Exit fullscreen mode

Top comments (0)