Skip to main content

NGINX

NGINX AJP Module: Proxy to Tomcat via AJP

by ,


We have by far the largest RPM repository with NGINX module packages and VMODs for Varnish. If you want to install NGINX, Varnish, and lots of useful performance/security software with smooth yum upgrades for production use, this is the repository for you.
Active subscription is required.

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_module directive 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 strong secret
  • The secret must match the ajp_secret directive 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_conn dramatically 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.

D

Danila Vershinin

Founder & Lead Engineer

NGINX configuration and optimizationLinux system administrationWeb performance engineering

10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

This site uses Akismet to reduce spam. Learn how your comment data is processed.