Configure Apache Tomcat 10 clustering with HAProxy load balancing to distribute traffic across multiple application server instances. Implement session replication and automatic failover for production-grade Java web applications with high availability and zero-downtime deployments.
Prerequisites
- Root or sudo access
- Minimum 4GB RAM
- Java application deployment experience
- Basic understanding of HTTP load balancing
What this solves
Tomcat clustering with HAProxy provides high availability and load distribution for Java web applications by running multiple Tomcat instances that share session data and automatically handle failover. This setup eliminates single points of failure, improves application performance through load distribution, and enables zero-downtime deployments for mission-critical applications.
Step-by-step configuration
Update system packages
Start by updating your package manager to ensure you have the latest security patches and package versions.
sudo apt update && sudo apt upgrade -y
Install Java 17 and required packages
Install OpenJDK 17 and HAProxy. Java 17 is the recommended LTS version for Tomcat 10 with improved performance and security features.
sudo apt install -y openjdk-17-jdk haproxy wget curl
Download and install Tomcat 10
Download the latest Tomcat 10 release and create the installation directory structure with proper permissions.
cd /opt
sudo wget https://archive.apache.org/dist/tomcat/tomcat-10/v10.1.28/bin/apache-tomcat-10.1.28.tar.gz
sudo tar -xzf apache-tomcat-10.1.28.tar.gz
sudo mv apache-tomcat-10.1.28 tomcat10
sudo useradd -r -s /bin/false tomcat
sudo chown -R tomcat:tomcat /opt/tomcat10
Create Tomcat cluster directories
Set up separate directories for each Tomcat instance in the cluster with proper ownership and permissions.
sudo mkdir -p /opt/tomcat-cluster/{node1,node2}
sudo cp -r /opt/tomcat10/* /opt/tomcat-cluster/node1/
sudo cp -r /opt/tomcat10/* /opt/tomcat-cluster/node2/
sudo chown -R tomcat:tomcat /opt/tomcat-cluster
sudo chmod +x /opt/tomcat-cluster/node1/bin/*.sh
sudo chmod +x /opt/tomcat-cluster/node2/bin/*.sh
Configure Tomcat Node 1
Configure the first Tomcat instance with clustering enabled, unique ports, and session replication settings.
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxThreads="200"
minSpareThreads="10" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost" jvmRoute="node1">
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
tempDir="/tmp/war-temp/"
deployDir="/tmp/war-deploy/"
watchDir="/tmp/war-listen/"
watchEnabled="false"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
Configure Tomcat Node 2
Configure the second Tomcat instance with different ports but same clustering configuration for session replication.
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8006" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8081" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8444"
maxThreads="200"
minSpareThreads="10" />
<Connector port="8010" protocol="AJP/1.3" redirectPort="8444" />
<Engine name="Catalina" defaultHost="localhost" jvmRoute="node2">
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="4001"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
tempDir="/tmp/war-temp/"
deployDir="/tmp/war-deploy/"
watchDir="/tmp/war-listen/"
watchEnabled="false"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
Create systemd services for Tomcat instances
Create systemd service files to manage each Tomcat instance with proper JVM settings and automatic restart capabilities.
[Unit]
Description=Apache Tomcat 10 Node 1
After=network.target
[Service]
Type=forking
User=tomcat
Group=tomcat
Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
Environment=CATALINA_PID=/opt/tomcat-cluster/node1/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/tomcat-cluster/node1
Environment=CATALINA_BASE=/opt/tomcat-cluster/node1
Environment="CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC"
Environment="JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom"
ExecStart=/opt/tomcat-cluster/node1/bin/startup.sh
ExecStop=/opt/tomcat-cluster/node1/bin/shutdown.sh
RestartSec=10
Restart=always
[Install]
WantedBy=multi-user.target
[Unit]
Description=Apache Tomcat 10 Node 2
After=network.target
[Service]
Type=forking
User=tomcat
Group=tomcat
Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
Environment=CATALINA_PID=/opt/tomcat-cluster/node2/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/tomcat-cluster/node2
Environment=CATALINA_BASE=/opt/tomcat-cluster/node2
Environment="CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC"
Environment="JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom"
ExecStart=/opt/tomcat-cluster/node2/bin/startup.sh
ExecStop=/opt/tomcat-cluster/node2/bin/shutdown.sh
RestartSec=10
Restart=always
[Install]
WantedBy=multi-user.target
Configure HAProxy load balancer
Configure HAProxy to distribute traffic between Tomcat instances with sticky sessions, health checks, and automatic failover.
global
log stdout local0
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
defaults
mode http
log global
option httplog
option dontlognull
option log-health-checks
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
frontend tomcat_frontend
bind *:80
bind *:443 ssl crt /etc/ssl/certs/example.com.pem
redirect scheme https if !{ ssl_fc }
# Add headers for backend servers
http-request set-header X-Forwarded-Proto https if { ssl_fc }
http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
default_backend tomcat_cluster
backend tomcat_cluster
balance roundrobin
# Enable sticky sessions using JSESSIONID cookie
cookie JSESSIONID prefix nocache
# Health check configuration
option httpchk GET /
http-check expect status 200
# Tomcat server definitions
server node1 127.0.0.1:8080 check cookie node1 inter 3000 fall 3 rise 2
server node2 127.0.0.1:8081 check cookie node2 inter 3000 fall 3 rise 2
# Session affinity backup
option redispatch
retries 3
HAProxy statistics page
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 30s
stats admin if TRUE
Create test application with session support
Deploy a simple test application that demonstrates session replication across the cluster.
sudo mkdir -p /opt/tomcat-cluster/node1/webapps/cluster-test
sudo mkdir -p /opt/tomcat-cluster/node2/webapps/cluster-test
<%@ page session="true" %>
<%@ page import="java.util.*" %>
<html>
<head>
<title>Tomcat Cluster Test</title>
</head>
<body>
<h1>Tomcat Cluster Session Test</h1>
<p><strong>Server Info:</strong></p>
<ul>
<li>Server: <%= request.getLocalAddr() + ":" + request.getLocalPort() %></li>
<li>JVM Route: <%= System.getProperty("jvmRoute", "unknown") %></li>
<li>Session ID: <%= session.getId() %></li>
<li>Session New: <%= session.isNew() %></li>
<li>Time: <%= new Date() %></li>
</ul>
<p><strong>Session Data:</strong></p>
<%
Integer visitCount = (Integer) session.getAttribute("visitCount");
if (visitCount == null) {
visitCount = 1;
} else {
visitCount++;
}
session.setAttribute("visitCount", visitCount);
%>
<p>Visit Count: <%= visitCount %></p>
<p><a href="index.jsp">Refresh Page</a></p>
</body>
</html>
sudo cp /opt/tomcat-cluster/node1/webapps/cluster-test/index.jsp /opt/tomcat-cluster/node2/webapps/cluster-test/
sudo chown -R tomcat:tomcat /opt/tomcat-cluster/node*/webapps/cluster-test
Add web.xml for session replication
Configure the web application to enable session replication across the cluster.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>Cluster Test Application</display-name>
<!-- Enable session replication -->
<distributable/>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
sudo mkdir -p /opt/tomcat-cluster/node2/webapps/cluster-test/WEB-INF
sudo cp /opt/tomcat-cluster/node1/webapps/cluster-test/WEB-INF/web.xml /opt/tomcat-cluster/node2/webapps/cluster-test/WEB-INF/
sudo chown -R tomcat:tomcat /opt/tomcat-cluster
Configure firewall rules
Open the necessary ports for HAProxy, Tomcat instances, and cluster communication.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 8404/tcp
sudo ufw allow 4000:4001/tcp
sudo ufw allow from 228.0.0.4
Start and enable services
Start both Tomcat instances and HAProxy, then enable them to start automatically on system boot.
sudo systemctl daemon-reload
sudo systemctl enable --now tomcat-node1
sudo systemctl enable --now tomcat-node2
sudo systemctl enable --now haproxy
Test cluster failover
Test session replication
Verify that sessions are replicated between cluster nodes and HAProxy handles failover correctly.
# Test the application
curl -c cookies.txt -b cookies.txt http://localhost/cluster-test/
Check HAProxy stats
curl http://localhost:8404/stats
Test failover by stopping one node
sudo systemctl stop tomcat-node1
curl -c cookies.txt -b cookies.txt http://localhost/cluster-test/
Restart the stopped node
sudo systemctl start tomcat-node1
Monitor cluster communication
Check the Tomcat logs to verify cluster membership and session replication.
sudo tail -f /opt/tomcat-cluster/node1/logs/catalina.out
sudo tail -f /opt/tomcat-cluster/node2/logs/catalina.out
sudo journalctl -u haproxy -f
Performance optimization
Tune JVM memory settings
Optimize JVM heap settings based on your application requirements and available system memory.
# Update the CATALINA_OPTS line for production workloads
Environment="CATALINA_OPTS=-Xms1024M -Xmx2048M -server -XX:+UseG1GC -XX:+UseStringDeduplication"
sudo systemctl daemon-reload
sudo systemctl restart tomcat-node1
sudo systemctl restart tomcat-node2
Optimize HAProxy configuration
Fine-tune HAProxy settings for better performance and connection handling.
# Add these optimizations to the backend section
backend tomcat_cluster
balance roundrobin
cookie JSESSIONID prefix nocache
# Performance optimizations
option httpchk GET /cluster-test/
http-check expect status 200
# Connection optimization
option http-server-close
option forwardfor
# Timeout optimization
timeout server 30s
timeout connect 3s
server node1 127.0.0.1:8080 check cookie node1 inter 5000 fall 2 rise 3 maxconn 300
server node2 127.0.0.1:8081 check cookie node2 inter 5000 fall 2 rise 3 maxconn 300
Verify your setup
# Check service status
sudo systemctl status tomcat-node1
sudo systemctl status tomcat-node2
sudo systemctl status haproxy
Test cluster connectivity
curl -I http://localhost/cluster-test/
curl http://localhost:8404/stats
Verify Tomcat clustering
sudo netstat -tlnp | grep :4000
sudo netstat -tlnp | grep :4001
Check cluster logs
sudo grep -i "member added" /opt/tomcat-cluster/node*/logs/catalina.out
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| 503 Service Unavailable | Tomcat instances not responding | Check systemctl status and logs: sudo journalctl -u tomcat-node1 |
| Sessions not replicating | Missing distributable tag in web.xml | Add <distributable/> to web.xml and restart |
| Cluster nodes not joining | Firewall blocking multicast | Open ports 4000-4001 and allow multicast address 228.0.0.4 |
| HAProxy health checks failing | Incorrect health check path | Verify health check URL matches deployed application path |
| High memory usage | JVM heap settings too large | Adjust -Xms and -Xmx values based on available RAM |
| SSL certificate errors | Missing or invalid certificate | Generate proper SSL certificate or use Let's Encrypt |
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Configuration
TOMCAT_VERSION="10.1.28"
TOMCAT_URL="https://archive.apache.org/dist/tomcat/tomcat-10/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz"
HAPROXY_PORT="${1:-80}"
NODE1_PORT="8080"
NODE2_PORT="8081"
# Usage message
usage() {
echo "Usage: $0 [haproxy_port]"
echo " haproxy_port: Port for HAProxy (default: 80)"
exit 1
}
# Error handling
cleanup() {
echo -e "${RED}Installation failed. Cleaning up...${NC}"
systemctl stop tomcat-node1 tomcat-node2 haproxy 2>/dev/null || true
rm -rf /opt/tomcat10 /opt/tomcat-cluster 2>/dev/null || true
userdel tomcat 2>/dev/null || true
}
trap cleanup ERR
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
JAVA_PKG="openjdk-17-jdk"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
JAVA_PKG="java-17-openjdk-devel"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
JAVA_PKG="java-17-amazon-corretto-devel"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
else
echo -e "${RED}Cannot detect distribution${NC}"
exit 1
fi
echo -e "${GREEN}Setting up Tomcat 10 clustering with HAProxy on $PRETTY_NAME${NC}"
# Step 1: Update system packages
echo -e "${YELLOW}[1/8] Updating system packages...${NC}"
$PKG_UPDATE
# Step 2: Install required packages
echo -e "${YELLOW}[2/8] Installing Java 17 and HAProxy...${NC}"
$PKG_INSTALL $JAVA_PKG haproxy wget curl
# Step 3: Download and install Tomcat 10
echo -e "${YELLOW}[3/8] Downloading and installing Tomcat 10...${NC}"
cd /opt
wget -q $TOMCAT_URL
tar -xzf apache-tomcat-${TOMCAT_VERSION}.tar.gz
mv apache-tomcat-${TOMCAT_VERSION} tomcat10
useradd -r -s /bin/false tomcat || true
chown -R tomcat:tomcat /opt/tomcat10
chmod 755 /opt/tomcat10/bin/*.sh
# Step 4: Create cluster directories
echo -e "${YELLOW}[4/8] Setting up cluster directories...${NC}"
mkdir -p /opt/tomcat-cluster/{node1,node2}
cp -r /opt/tomcat10/* /opt/tomcat-cluster/node1/
cp -r /opt/tomcat10/* /opt/tomcat-cluster/node2/
chown -R tomcat:tomcat /opt/tomcat-cluster
chmod 755 /opt/tomcat-cluster/node1/bin/*.sh
chmod 755 /opt/tomcat-cluster/node2/bin/*.sh
# Step 5: Configure Tomcat nodes
echo -e "${YELLOW}[5/8] Configuring Tomcat cluster nodes...${NC}"
# Node 1 configuration
cat > /opt/tomcat-cluster/node1/conf/server.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost" jvmRoute="node1">
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService" address="228.0.0.4" port="45564" frequency="500" dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="auto" port="4000" autoBind="100" selectorTimeout="5000" maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
EOF
# Node 2 configuration
cat > /opt/tomcat-cluster/node2/conf/server.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8006" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<Service name="Catalina">
<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost" jvmRoute="node2">
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService" address="228.0.0.4" port="45564" frequency="500" dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="auto" port="4001" autoBind="100" selectorTimeout="5000" maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
EOF
# Step 6: Configure HAProxy
echo -e "${YELLOW}[6/8] Configuring HAProxy...${NC}"
cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.backup
cat > /etc/haproxy/haproxy.cfg << EOF
global
daemon
maxconn 4096
defaults
mode http
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
option httpchk GET /
balance roundrobin
frontend tomcat_frontend
bind *:${HAPROXY_PORT}
default_backend tomcat_servers
backend tomcat_servers
cookie JSESSIONID prefix nocache
server node1 127.0.0.1:8080 check cookie node1
server node2 127.0.0.1:8081 check cookie node2
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 30s
EOF
# Step 7: Create systemd services
echo -e "${YELLOW}[7/8] Creating systemd services...${NC}"
# Tomcat Node 1 service
cat > /etc/systemd/system/tomcat-node1.service << 'EOF'
[Unit]
Description=Tomcat Node 1
After=network.target
[Service]
Type=forking
User=tomcat
Group=tomcat
Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk
Environment=CATALINA_PID=/opt/tomcat-cluster/node1/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/tomcat-cluster/node1
Environment=CATALINA_BASE=/opt/tomcat-cluster/node1
Environment="CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC"
Environment="JAVA_OPTS=-Djava.security.egd=file:///dev/urandom"
ExecStart=/opt/tomcat-cluster/node1/bin/startup.sh
ExecStop=/opt/tomcat-cluster/node1/bin/shutdown.sh
RestartSec=10
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# Tomcat Node 2 service
cat > /etc/systemd/system/tomcat-node2.service << 'EOF'
[Unit]
Description=Tomcat Node 2
After=network.target
[Service]
Type=forking
User=tomcat
Group=tomcat
Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk
Environment=CATALINA_PID=/opt/tomcat-cluster/node2/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/tomcat-cluster/node2
Environment=CATALINA_BASE=/opt/tomcat-cluster/node2
Environment="CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC"
Environment="JAVA_OPTS=-Djava.security.egd=file:///dev/urandom"
ExecStart=/opt/tomcat-cluster/node2/bin/startup.sh
ExecStop=/opt/tomcat-cluster/node2/bin/shutdown.sh
RestartSec=10
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# Configure firewall
if command -v ufw &> /dev/null; then
ufw allow ${HAPROXY_PORT}/tcp
ufw allow 8080/tcp
ufw allow 8081/tcp
ufw allow 8404/tcp
elif command -v firewall-cmd &> /dev/null; then
firewall-cmd --permanent --add-port=${HAPROXY_PORT}/tcp
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --permanent --add-port=8081/tcp
firewall-cmd --permanent --add-port=8404/tcp
firewall-cmd --reload
fi
# Step 8: Start services
echo -e "${YELLOW}[8/8] Starting services...${NC}"
systemctl daemon-reload
systemctl enable tomcat-node1 tomcat-node2 haproxy
systemctl start tomcat-node1 tomcat-node2 haproxy
# Verification
echo -e "${YELLOW}Verifying installation...${NC}"
sleep 10
if systemctl is-active --quiet tomcat-node1; then
echo -e "${GREEN}✓ Tomcat Node 1 is running${NC}"
else
echo -e "${RED}✗ Tomcat Node 1 failed to start${NC}"
fi
if systemctl is-active --quiet tomcat-node2; then
echo -e "${GREEN}✓ Tomcat Node 2 is running${NC}"
else
echo -e "${RED}✗ Tomcat Node 2 failed to start${NC}"
fi
if systemctl is-active --quiet haproxy; then
echo -e "${GREEN}✓ HAProxy is running${NC}"
else
echo -e "${RED}✗ HAProxy failed to start${NC}"
fi
echo -e "${GREEN}Installation completed successfully!${NC}"
echo -e "${YELLOW}Access your application at: http://$(hostname -I | awk '{print $1}'):${HAPROXY_PORT}${NC}"
echo -e "${YELLOW}HAProxy stats available at: http://$(hostname -I | awk '{print $1}'):8404/stats${NC}"
Review the script before running. Execute with: bash install.sh