Skip to main content

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.

ValueNameDescription
RRequestsRequest count based Round Robin (default)
TTrafficByte-based distribution
BBusynessCurrent active requests based (Least Connection)
SSessionsSession count based distribution
NNextPure 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​

Featuremod_jkmod_proxy_ajp
Management UIjk-status page providedbalancer-manager
ConfigurationSeparate workers.propertiesInline in httpd.conf
AlgorithmsR/T/B/S/Nbyrequests/bytraffic/bybusyness
Sticky SessionAutomatic jvmRoute integrationManual stickysession setting
MaturityVery high (older module)High
Configuration complexityComplex (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.