mod_proxy_ajp Configuration
mod_proxy_ajp applies Apache's built-in proxy module to the AJP protocol. Configuration is simpler than mod_jk and uses Apache standard modules, but because it uses AJP, configuring Ghostcat vulnerability mitigations is mandatory.
Enabling the Module
sudo a2enmod proxy
sudo a2enmod proxy_ajp
sudo systemctl reload apache2
# Verify
apache2ctl -M | grep "proxy_ajp"
# proxy_ajp_module (shared)
Basic Configuration
# /etc/apache2/sites-available/myapp.conf
<VirtualHost *:80>
ServerName example.com
ProxyRequests Off # Disable forward proxy (security)
ProxyPreserveHost On # Preserve original Host header
# AJP proxy
ProxyPass / ajp://127.0.0.1:8009/
ProxyPassReverse / ajp://127.0.0.1:8009/
</VirtualHost>
Ghostcat Security Configuration (Required)
Tomcat server.xml
<Connector protocol="AJP/1.3"
address="127.0.0.1" <!-- Listen locally only -->
port="8009"
redirectPort="8443"
secretRequired="true" <!-- Secret required -->
secret="ApacheAjpSecret2024!" <!-- Strong secret -->
maxThreads="200"
connectionTimeout="20000"/>
Include Secret in Apache Configuration
<VirtualHost *:80>
ServerName example.com
ProxyRequests Off
ProxyPreserveHost On
# AJP connection with secret
<Proxy "ajp://127.0.0.1:8009?secret=ApacheAjpSecret2024!">
Require all granted
</Proxy>
ProxyPass / "ajp://127.0.0.1:8009/?secret=ApacheAjpSecret2024!"
ProxyPassReverse / ajp://127.0.0.1:8009/
</VirtualHost>
Forwarding Real Client IP (mod_remoteip)
sudo a2enmod remoteip
<VirtualHost *:80>
ServerName example.com
# Set actual client IP as REMOTE_ADDR
RemoteIPHeader X-Forwarded-For
ProxyRequests Off
ProxyPreserveHost On
# Real IP forwarding headers
RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
RequestHeader set X-Forwarded-Proto "http"
ProxyPass / "ajp://127.0.0.1:8009/?secret=MySecret"
ProxyPassReverse / ajp://127.0.0.1:8009/
</VirtualHost>
SSL + mod_proxy_ajp
<VirtualHost *:80>
ServerName example.com
Redirect permanent / https://example.com/
</VirtualHost>
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# Notify Tomcat that this is HTTPS (request.isSecure() = true)
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
ProxyRequests Off
ProxyPreserveHost On
<Proxy "ajp://127.0.0.1:8009?secret=ApacheAjpSecret2024!">
Require all granted
</Proxy>
ProxyPass / "ajp://127.0.0.1:8009/?secret=ApacheAjpSecret2024!"
ProxyPassReverse / ajp://127.0.0.1:8009/
</VirtualHost>
Static File Separation
<VirtualHost *:443>
ServerName example.com
# Static files — served directly by Apache
Alias /static /var/www/myapp/static
<Directory "/var/www/myapp/static">
Options -Indexes
AllowOverride None
Require all granted
ExpiresActive On
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType image/png "access plus 1 month"
</Directory>
# Static paths are not passed to AJP
ProxyPass /static !
ProxyPass /favicon.ico !
ProxyPass /robots.txt !
# Dynamic requests → Tomcat
ProxyPass / "ajp://127.0.0.1:8009/?secret=MySecret"
ProxyPassReverse / ajp://127.0.0.1:8009/
</VirtualHost>
Load Balancing (mod_proxy_balancer)
sudo a2enmod proxy_balancer
sudo a2enmod lbmethod_byrequests
sudo a2enmod headers
<VirtualHost *:443>
ServerName example.com
# Define load balancer
<Proxy "balancer://tomcatcluster">
BalancerMember "ajp://127.0.0.1:8009?secret=MySecret" route=tomcat1
BalancerMember "ajp://192.168.1.11:8009?secret=MySecret" route=tomcat2
ProxySet lbmethod=byrequests
ProxySet stickysession=JSESSIONID
</Proxy>
ProxyPass / balancer://tomcatcluster/
ProxyPassReverse / balancer://tomcatcluster/
# Balancer status page (internal only)
<Location /balancer-manager>
SetHandler balancer-manager
Require ip 127.0.0.1
</Location>
</VirtualHost>
Timeout Tuning
<VirtualHost *:443>
# AJP connection timeout
ProxyTimeout 60
# Per-path timeout (ProxyPass attributes)
ProxyPass / "ajp://127.0.0.1:8009/?secret=MySecret" \
timeout=60 \
connectiontimeout=10 \
retry=1 \
acquire=3000
</VirtualHost>
| Attribute | Description | Default |
|---|---|---|
timeout | AJP response wait time (seconds) | Apache ProxyTimeout |
connectiontimeout | Connection establishment wait (seconds) | timeout value |
retry | Wait time before retry after failure (seconds) | 60 |
acquire | Max wait for connection pool acquisition (ms) | Unlimited |
Diagnostics
# Check AJP connection
ss -tlnp | grep 8009
# Apache error log
tail -f /var/log/apache2/error.log
# Example mod_proxy_ajp errors
# [error] ajp_read_header: ajp_ilink_receive failed
# → Restart Tomcat or check AJP connection pool exhaustion
# Validate configuration
sudo apache2ctl configtest
Summary
| Item | Configuration |
|---|---|
| Required modules | proxy + proxy_ajp |
| Ghostcat mitigation | Tomcat: secretRequired=true + Apache: ?secret=... |
| Listen restriction | Tomcat: address="127.0.0.1" |
| SSL | RequestHeader set X-Forwarded-Proto "https" |
| Load balancing | proxy_balancer + stickysession=JSESSIONID |
| Recommended migration | Use mod_proxy_http for new systems |