The NGINX AJP module enables NGINX to communicate directly with Apache Tomcat and other Java application servers using the Apache JServ Protocol (AJP). This integration combines NGINX’s exceptional performance with Tomcat’s robust Java servlet capabilities. The result is an optimal architecture for production Java web applications.
What is the AJP Protocol?
The Apache JServ Protocol (AJP) is a binary protocol designed for communication between a web server and a Java application server. Unlike HTTP proxying, AJP offers several advantages:
- Binary protocol efficiency: AJP transmits data in a compact binary format, reducing parsing overhead
- Persistent connections: AJP maintains long-lived connections, eliminating connection setup overhead
- Header optimization: Common HTTP headers are encoded as small integers, reducing bandwidth
- Authentication support: AJP includes built-in secret-based authentication for secure communication
The NGINX AJP module brings these benefits to NGINX. You can leverage NGINX’s superior browser caching for static files, SSL termination, and load balancing while routing dynamic requests to Tomcat efficiently.
Why Use the NGINX AJP Module?
While NGINX’s built-in proxy_pass directive can proxy requests to Tomcat over HTTP, the AJP protocol offers distinct advantages:
| Feature | AJP Proxy | HTTP Proxy |
|---|---|---|
| Protocol overhead | Lower (binary) | Higher (text) |
| Connection reuse | Native support | Requires keepalive tuning |
| Header transmission | Optimized integers | Full text headers |
| Backend authentication | Built-in secret | Additional configuration needed |
| CPU usage | Lower | Higher |
For high-traffic Java applications, these differences translate into measurable performance improvements.
Installation
RHEL, CentOS, AlmaLinux, Rocky Linux
Install the GetPageSpeed repository and the NGINX AJP module:
sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-ajp
Then load the module by adding the following line at the top of /etc/nginx/nginx.conf:
load_module modules/ngx_http_ajp_module.so;
Debian and Ubuntu
First, set up the GetPageSpeed APT repository, then install:
sudo apt-get update
sudo apt-get install nginx-module-ajp
On Debian/Ubuntu, the package handles module loading automatically. No
load_moduledirective is needed.
For detailed package information, visit the NGINX AJP module page.
Configuring Tomcat for AJP
Before configuring NGINX, you must enable the AJP connector in Tomcat. Edit your Tomcat’s server.xml and add the AJP connector:
<Connector protocol="AJP/1.3"
address="127.0.0.1"
port="8009"
redirectPort="8443"
secretRequired="true"
secret="your_secure_secret_here"
maxParameterCount="1000" />
Important security notes:
- Always set
address="127.0.0.1"to bind AJP only to localhost - Enable
secretRequired="true"and set a strongsecret - The
secretmust match theajp_secretdirective in NGINX
Restart Tomcat after making changes:
sudo systemctl restart tomcat
Verify the AJP port is listening:
ss -tlnp | grep 8009
Basic NGINX Configuration
Here is a minimal configuration to proxy requests through the NGINX AJP module:
upstream tomcat {
server 127.0.0.1:8009;
keepalive 10;
}
server {
listen 80;
server_name example.com;
location / {
ajp_pass tomcat;
ajp_secret your_secure_secret_here;
ajp_keep_conn on;
}
}
The ajp_pass directive specifies the upstream server. The ajp_secret provides authentication. The ajp_keep_conn on enables persistent connections for better performance.
Production-Ready Configuration
For production deployments, use this comprehensive configuration. Every directive has been tested and verified:
upstream tomcat_backend {
server 127.0.0.1:8009;
keepalive 32;
}
# Response caching zone
ajp_cache_path /var/cache/nginx/ajp_cache levels=1:2
keys_zone=ajp_cache:10m inactive=60m max_size=1g;
server {
listen 80;
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# Critical: Set writable temp path for AJP buffering
ajp_temp_path /var/cache/nginx/ajp_temp 1 2;
# Serve static files directly from NGINX
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2?)$ {
root /var/www/html;
expires 30d;
access_log off;
}
# Cacheable API responses
location /api/public {
ajp_pass tomcat_backend;
ajp_secret your_secure_secret_here;
ajp_keep_conn on;
# Enable caching
ajp_cache ajp_cache;
ajp_cache_key "$uri$is_args$args";
ajp_cache_valid 200 302 10m;
ajp_cache_valid 404 1m;
# Prevent thundering herd
ajp_cache_lock on;
ajp_cache_lock_timeout 5s;
# Serve stale on errors
ajp_cache_use_stale error timeout updating http_500 http_503;
# Don't cache responses with cookies
ajp_hide_header Set-Cookie;
# Debug header
add_header X-Cache-Status $upstream_cache_status always;
}
# Dynamic content (not cached)
location / {
ajp_pass tomcat_backend;
ajp_secret your_secure_secret_here;
ajp_keep_conn on;
# Timeout settings
ajp_connect_timeout 60s;
ajp_read_timeout 300s;
ajp_send_timeout 60s;
# Buffer settings
ajp_buffer_size 8k;
ajp_buffers 8 8k;
ajp_busy_buffers_size 16k;
# Pass client information to Tomcat
ajp_set_header X-Forwarded-Proto $scheme;
ajp_set_header X-Real-IP $remote_addr;
ajp_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
ajp_set_header Host $host;
# Failover settings
ajp_next_upstream error timeout http_500 http_503;
# Error handling
ajp_intercept_errors on;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Important: Create the cache directories before starting NGINX:
mkdir -p /var/cache/nginx/ajp_cache /var/cache/nginx/ajp_temp
chown nginx:nginx /var/cache/nginx/ajp_cache /var/cache/nginx/ajp_temp
Verifying Headers Are Passed Correctly
Create a test JSP to verify headers reach Tomcat:
<%@ page contentType="text/html;charset=UTF-8" %>
<ul>
<li>X-Forwarded-Proto: <%= request.getHeader("X-Forwarded-Proto") %></li>
<li>X-Real-IP: <%= request.getHeader("X-Real-IP") %></li>
<li>X-Forwarded-For: <%= request.getHeader("X-Forwarded-For") %></li>
<li>Remote Address: <%= request.getRemoteAddr() %></li>
</ul>
Expected output through NGINX AJP:
X-Forwarded-Proto: http
X-Real-IP: 127.0.0.1
X-Forwarded-For: 127.0.0.1
Remote Address: 127.0.0.1
Directive Reference
Core Directives
ajp_pass
Syntax: ajp_pass address
Context: location, if in location
Specifies the AJP server. Accepts an upstream name, direct address, or Unix socket:
ajp_pass tomcat_backend;
ajp_pass 127.0.0.1:8009;
ajp_pass unix:/var/run/tomcat-ajp.sock;
ajp_secret
Syntax: ajp_secret secret
Context: location, if in location
Sets the AJP secret for authentication. Requests with wrong secrets receive HTTP 403:
ajp_secret my_secure_secret_123;
ajp_keep_conn
Syntax: ajp_keep_conn on | off
Default: ajp_keep_conn off
Context: http, server, location
Enables persistent connections. Use with upstream keepalive:
upstream tomcat {
server 127.0.0.1:8009;
keepalive 10;
}
location / {
ajp_pass tomcat;
ajp_keep_conn on;
}
Timeout Directives
ajp_connect_timeout
Syntax: ajp_connect_timeout time
Default: ajp_connect_timeout 60s
Context: http, server, location
Sets the connection timeout. Cannot exceed 75 seconds.
ajp_read_timeout
Syntax: ajp_read_timeout time
Default: ajp_read_timeout 60s
Context: http, server, location
How long NGINX waits for a response. Increase for long operations:
ajp_read_timeout 300s; # 5 minutes
ajp_send_timeout
Syntax: ajp_send_timeout time
Default: ajp_send_timeout 60s
Context: http, server, location
Timeout between successive write operations.
Buffer Directives
ajp_buffer_size
Syntax: ajp_buffer_size size
Default: ajp_buffer_size 4k|8k
Context: http, server, location
Buffer size for the first response part (headers).
ajp_buffers
Syntax: ajp_buffers number size
Default: ajp_buffers 8 4k|8k
Context: http, server, location
Number and size of response buffers:
ajp_buffers 16 8k;
ajp_busy_buffers_size
Syntax: ajp_busy_buffers_size size
Default: ajp_busy_buffers_size 8k|16k
Context: http, server, location
Limits buffers busy sending to the client.
ajp_temp_path
Syntax: ajp_temp_path path [level1 [level2 [level3]]]
Default: ajp_temp_path ajp_temp
Context: http, server, location
Critical: Sets the directory for temporary files. Ensure this path is writable:
ajp_temp_path /var/cache/nginx/ajp_temp 1 2;
Header Directives
ajp_set_header
Syntax: ajp_set_header name value
Context: http, server, location
Add or modify request headers:
ajp_set_header X-Forwarded-Proto $scheme;
ajp_set_header X-Real-IP $remote_addr;
ajp_pass_header
Syntax: ajp_pass_header name
Context: http, server, location
Pass specific headers from AJP response. “Status” and “X-Accel-*” are hidden by default.
ajp_hide_header
Syntax: ajp_hide_header name
Context: http, server, location
Hide headers from the client:
ajp_hide_header X-Powered-By;
ajp_hide_header Server;
Caching Directives
ajp_cache_path
Syntax: ajp_cache_path path [levels=levels] keys_zone=name:size [inactive=time] ...
Context: http
Define cache storage:
ajp_cache_path /var/cache/nginx/ajp levels=1:2
keys_zone=ajp_cache:10m inactive=60m max_size=1g;
ajp_cache
Syntax: ajp_cache zone | off
Default: ajp_cache off
Context: http, server, location
Enable caching:
ajp_cache ajp_cache;
ajp_cache_key
Syntax: ajp_cache_key string
Context: http, server, location
Cache key definition:
ajp_cache_key "$scheme$host$request_uri";
ajp_cache_valid
Syntax: ajp_cache_valid [code ...] time
Context: http, server, location
Caching time per response code:
ajp_cache_valid 200 302 10m;
ajp_cache_valid 404 1m;
ajp_cache_lock
Syntax: ajp_cache_lock on | off
Default: ajp_cache_lock off
Context: http, server, location
Prevents thundering herd. Only one request populates a cache entry:
ajp_cache_lock on;
ajp_cache_lock_timeout 5s;
ajp_cache_lock_timeout
Syntax: ajp_cache_lock_timeout time
Default: ajp_cache_lock_timeout 5s
Context: http, server, location
Maximum time to wait for cache lock.
ajp_cache_use_stale
Syntax: ajp_cache_use_stale error | timeout | updating | ... | off
Default: ajp_cache_use_stale off
Context: http, server, location
Serve stale content on errors:
ajp_cache_use_stale error timeout updating http_500 http_503;
Error Handling Directives
ajp_intercept_errors
Syntax: ajp_intercept_errors on | off
Default: ajp_intercept_errors off
Context: http, server, location
Intercept 4xx/5xx errors:
ajp_intercept_errors on;
error_page 500 502 503 504 /50x.html;
ajp_next_upstream
Syntax: ajp_next_upstream error | timeout | invalid_header | http_500 | http_503 | http_404 | off
Default: ajp_next_upstream error timeout
Context: http, server, location
When to try the next upstream:
ajp_next_upstream error timeout http_500 http_503;
Load Balancing with Multiple Tomcat Instances
The NGINX AJP module integrates with NGINX upstream for load balancing:
upstream tomcat_cluster {
server 192.168.1.10:8009 weight=3;
server 192.168.1.11:8009 weight=2;
server 192.168.1.12:8009 backup;
keepalive 64;
}
server {
listen 80;
server_name app.example.com;
location / {
ajp_pass tomcat_cluster;
ajp_secret cluster_secret;
ajp_keep_conn on;
ajp_next_upstream error timeout http_500 http_503;
ajp_connect_timeout 10s;
}
}
Performance Tips
Connection Pooling
Always use keepalive with ajp_keep_conn on:
upstream tomcat {
server 127.0.0.1:8009;
keepalive 32;
}
With keepalive enabled, response times drop dramatically (from ~10ms to ~1ms).
Cache Lock for High Traffic
Enable ajp_cache_lock to prevent multiple simultaneous backend requests for the same uncached content. In testing, 5 concurrent requests for a 2-second page all completed in ~2 seconds instead of queuing.
Security Best Practices
Always Use AJP Secret
Generate a secure secret:
openssl rand -base64 32
Never expose AJP without authentication. Wrong secrets return HTTP 403.
Restrict AJP Binding
Configure Tomcat to listen only on localhost:
<Connector protocol="AJP/1.3"
address="127.0.0.1"
port="8009"
secretRequired="true"
secret="your_secret" />
SELinux Configuration
On SELinux-enabled systems:
setsebool -P httpd_can_network_connect 1
For more security hardening, see our guide on TLS 1.3 hardening.
Troubleshooting
Permission Denied for ajp_temp
If you see:
mkdir() "/etc/nginx/ajp_temp/..." failed (13: Permission denied)
Set proper ajp_temp_path with writable directory:
ajp_temp_path /var/cache/nginx/ajp_temp 1 2;
Create and set permissions:
mkdir -p /var/cache/nginx/ajp_temp
chown nginx:nginx /var/cache/nginx/ajp_temp
Connection Refused
Check Tomcat AJP is running:
ss -tlnp | grep 8009
Permission Denied (SELinux)
setsebool -P httpd_can_network_connect 1
Authentication Failed (HTTP 403)
Verify ajp_secret matches Tomcat’s secret attribute exactly.
Check AJP Connectivity
nc -zv 127.0.0.1 8009
Conclusion
The NGINX AJP module provides high-performance, secure integration with Apache Tomcat. Key benefits:
- Binary AJP protocol reduces CPU and bandwidth overhead
- Connection pooling with
ajp_keep_conndramatically improves response times - Built-in caching offloads Tomcat and improves scalability
- Secret authentication secures backend communication
For more information, visit the NGINX AJP module GitHub repository.

