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.