mod_jk Load Balancing: AJP-based Tomcat Clustering
mod_jk connects Apache HTTPD and Tomcat via the AJP protocol and is one of the most mature and stable Tomcat clustering methods. Define clusters in workers.properties with precise control over weights (lbfactor), routing, and session affinity.
Installing mod_jkβ
# Ubuntu/Debian
sudo apt-get install libapache2-mod-jk
# CentOS/RHEL
sudo yum install mod_jk
# Verify installation
apache2ctl -M | grep jk
# Output: jk_module (shared)
workers.properties Complete Guideβ
workers.properties is the core configuration file for mod_jk. Define Tomcat instances (workers) and load balancers here.
File Locationβ
# Default location
/etc/libapache2-mod-jk/workers.properties
# Or specify directly in apache2.conf or jk.conf
JkWorkersFile /etc/apache2/workers.properties
Basic Cluster Configuration (workers.properties)β
# ========================
# Worker list definition
# ========================
worker.list=loadbalancer,status
# ========================
# Tomcat Instance 1 (jvm1)
# ========================
worker.jvm1.type=ajp13
worker.jvm1.host=10.0.0.1
worker.jvm1.port=8009
worker.jvm1.lbfactor=3 # Weight: higher = more requests
worker.jvm1.connection_pool_size=20
worker.jvm1.connection_pool_timeout=600
worker.jvm1.socket_timeout=10
worker.jvm1.socket_keepalive=True
worker.jvm1.ping_timeout=10000 # ms
worker.jvm1.ping_mode=A # A=always ping, C=on connect, I=interval
# ========================
# Tomcat Instance 2 (jvm2)
# ========================
worker.jvm2.type=ajp13
worker.jvm2.host=10.0.0.2
worker.jvm2.port=8009
worker.jvm2.lbfactor=3
worker.jvm2.connection_pool_size=20
worker.jvm2.connection_pool_timeout=600
worker.jvm2.socket_timeout=10
worker.jvm2.socket_keepalive=True
worker.jvm2.ping_mode=A
# ========================
# Tomcat Instance 3 (jvm3, standby)
# ========================
worker.jvm3.type=ajp13
worker.jvm3.host=10.0.0.3
worker.jvm3.port=8009
worker.jvm3.lbfactor=1
worker.jvm3.connection_pool_size=10
worker.jvm3.socket_timeout=10
# ========================
# Load balancer definition
# ========================
worker.loadbalancer.type=lb
worker.loadbalancer.balance_workers=jvm1,jvm2,jvm3
worker.loadbalancer.sticky_session=True # Enable session affinity
worker.loadbalancer.sticky_session_force=False # Allow failover if session server dies
worker.loadbalancer.method=R # R=requests, T=traffic, B=busyness
worker.loadbalancer.lock=O # O=optimistic locking (performance first)
# ========================
# Status monitoring worker
# ========================
worker.status.type=status
lbfactor (Weight) Behaviorβ
jvm1: lbfactor=3 β handles 3/7 of total (about 43%)
jvm2: lbfactor=3 β handles 3/7 of total (about 43%)
jvm3: lbfactor=1 β handles 1/7 of total (about 14%)
The ratio matters, not the absolute values. 3:3:1 and 30:30:10 behave identically.
method (Load Balancing Algorithm)β
Select the distribution algorithm with worker.loadbalancer.method.
| Value | Name | Description |
|---|---|---|
R | Requests | Request count based Round Robin (default) |
T | Traffic | Byte-based distribution |
B | Busyness | Current active requests based (Least Connection) |
S | Sessions | Session count based distribution |
N | Next | Pure Round Robin |
# Least connection method (recommended for API servers)
worker.loadbalancer.method=B
Tomcat server.xml Integration: jvmRouteβ
For Sticky Session to work, Tomcat's jvmRoute must match the worker name in workers.properties.
Tomcat 1 (10.0.0.1) server.xmlβ
<!-- /opt/tomcat/conf/server.xml -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
<!-- jvmRoute must match the worker name -->
</Engine>
Tomcat 2 (10.0.0.2) server.xmlβ
<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm2">
</Engine>
Tomcat then appends routing information to session IDs:
Set-Cookie: JSESSIONID=1A2B3C4D5E6F.jvm1; Path=/; HttpOnly
Apache mod_jk reads the .jvm1 part and routes to that worker.
Apache Configuration: JkMountβ
Configure mod_jk in httpd.conf or VirtualHost.
# /etc/apache2/conf-available/jk.conf
JkWorkersFile /etc/apache2/workers.properties
JkLogFile /var/log/apache2/mod_jk.log
JkLogLevel info
JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"
# /etc/apache2/sites-available/app.conf
<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/html
# Dynamic requests: forward to Tomcat cluster
JkMount /app/* loadbalancer
JkMount /api/* loadbalancer
JkMount /*.jsp loadbalancer
JkMount /*.do loadbalancer
# Static files: exclude (Apache handles them)
JkUnMount /static/* loadbalancer
JkUnMount /images/* loadbalancer
JkUnMount *.css loadbalancer
JkUnMount *.js loadbalancer
# Status page
<Location "/jk-status">
JkMount /jk-status status
Require ip 127.0.0.1 10.0.0.0/8
</Location>
</VirtualHost>
Runtime Server Control via jk-statusβ
# Access jk-status web UI
http://localhost/jk-status
# Disable server via CLI
curl "http://localhost/jk-status?cmd=update&mime=txt&w=loadbalancer&sw=jvm3&wa=d"
# Enable server via CLI
curl "http://localhost/jk-status?cmd=update&mime=txt&w=loadbalancer&sw=jvm3&wa=a"
# Check current status (text format)
curl "http://localhost/jk-status?cmd=show&mime=txt"
Production workers.properties (Complete Version)β
worker.list=loadbalancer,status
# βββ Tomcat 1 ββββββββββββββββββββββββββββββββββββ
worker.jvm1.type=ajp13
worker.jvm1.host=10.0.0.1
worker.jvm1.port=8009
worker.jvm1.secret=myAjpSecret # AJP secret (security requirement)
worker.jvm1.lbfactor=2
worker.jvm1.connection_pool_size=25
worker.jvm1.connection_pool_timeout=600
worker.jvm1.socket_keepalive=True
worker.jvm1.ping_mode=A
worker.jvm1.ping_timeout=5000
worker.jvm1.prepost_timeout=5000
worker.jvm1.recover_time=60 # Retry interval after failure (seconds)
# βββ Tomcat 2 ββββββββββββββββββββββββββββββββββββ
worker.jvm2.type=ajp13
worker.jvm2.host=10.0.0.2
worker.jvm2.port=8009
worker.jvm2.secret=myAjpSecret
worker.jvm2.lbfactor=2
worker.jvm2.connection_pool_size=25
worker.jvm2.connection_pool_timeout=600
worker.jvm2.socket_keepalive=True
worker.jvm2.ping_mode=A
worker.jvm2.ping_timeout=5000
worker.jvm2.prepost_timeout=5000
worker.jvm2.recover_time=60
# βββ Hot Standby Tomcat βββββββββββββββββββββββββββ
worker.jvm3.type=ajp13
worker.jvm3.host=10.0.0.3
worker.jvm3.port=8009
worker.jvm3.secret=myAjpSecret
worker.jvm3.lbfactor=1
worker.jvm3.activation=f # f=forced standby (unused in normal operation)
worker.jvm3.connection_pool_size=5
worker.jvm3.recover_time=60
# βββ Load Balancer ββββββββββββββββββββββββββββββββ
worker.loadbalancer.type=lb
worker.loadbalancer.balance_workers=jvm1,jvm2,jvm3
worker.loadbalancer.sticky_session=True
worker.loadbalancer.sticky_session_force=False
worker.loadbalancer.method=B
worker.loadbalancer.lock=O
# βββ Status Worker ββββββββββββββββββββββββββββββββ
worker.status.type=status
worker.status.read_only=True
mod_jk vs mod_proxy_ajp Comparisonβ
| Feature | mod_jk | mod_proxy_ajp |
|---|---|---|
| Management UI | jk-status page provided | balancer-manager |
| Configuration | Separate workers.properties | Inline in httpd.conf |
| Algorithms | R/T/B/S/N | byrequests/bytraffic/bybusyness |
| Sticky Session | Automatic jvmRoute integration | Manual stickysession setting |
| Maturity | Very high (older module) | High |
| Configuration complexity | Complex (separate file) | Relatively simple |
Recommendation: Use mod_proxy_ajp or mod_proxy_http for new projects. Keep mod_jk for existing legacy environments.
The next page covers health check configuration for automatic detection and removal of failed nodes.