Skip to content

TinyActive/ja4-nginx-module

Repository files navigation

JA4 Nginx Module

Overview

This Nginx module implements JA4, JA4H, JA4TCP, and JA4one fingerprinting standards for HTTP and TLS clients. It enables you to identify and fingerprint incoming requests based on their TLS handshake (Client Hello), HTTP headers, and TCP characteristics.

image

This module is designed for:

  • Security: Detecting bots, scrapers, and malicious tools.
  • Access Control: Blocking or allowing requests based on fingerprints.
  • Analytics: Logging client types for traffic analysis.

⚠️ Custom Implementation Note

Important: This implementation uses Full SHA256 Hashes (64 characters) for the fingerprint components instead of the standard truncated 12-character hashes defined in the original JA4 spec.

  • Why? To provide maximum collision resistance and complete cryptographic detail as per user requirements.
  • Result: Fingerprints generated by this module will be significantly longer than standard JA4 strings.

Features

  • JA4 (TLS): Fingerprints the TLS Client Hello (Version, Cipher Suites, Extensions, etc.).
  • JA4H (HTTP): Fingerprints HTTP headers (Method, Version, Cookie, Header Order).
  • JA4S (HTTP/2): Fingerprints HTTP/2 connection state (Window Size, Frame Size).
  • JA4TCP (TCP): Fingerprints TCP characteristics (Window Size, Options, Flags, TTL).
  • JA4one (Composite): A combined fingerprint (JA4_JA4H) providing a holistic view of the client (TLS + HTTP).
  • Access Control Directives: Native Nginx directives to block/allow traffic.
  • Variables: Exposes fingerprints as Nginx variables for logging or Lua scripting.

Performance Benchmark

The following benchmark demonstrates the impact of the JA4 module on Nginx performance. Conclusion: The module is highly optimized and introduces negligible or no measurable overhead in typical scenarios. In some test runs, the module path may even appear faster due to system variance, proving it does not block or significantly slow down request processing.

Test Environment:

  • Connections: 100 concurrent
  • Duration: 30s
  • Threads: 4
Metric Meaning (Goal) Without Module (Baseline) With Module Performance Delta Interpretation
Requests/sec Capacity (Higher is Better) 12,817 13,321 +3.93% ✅ No Degradation
Avg Latency Speed (Lower is Better) 9.34ms 8.80ms -0.54ms ✅ No Degradation
p99 Latency Slowest 1% (Lower is Better) 24.33ms 22.45ms -1.88ms ✅ No Degradation

Note: Benchmarks run on a virtualized environment; minor variance (<5%) is expected.

Installation

Dependencies

  • Nginx source code (tested with 1.27.3+)
  • OpenSSL (development headers)

Compilation

Configure Nginx with the --add-module flag pointing to this directory:

./configure --with-compat --add-module=/path/to/ja4-nginx-module --with-http_ssl_module
make
make install

Configuration

1. Variables and Logging

The module exposes the following variables:

  • $http_ssl_ja4: The TLS fingerprint.
  • $http_ssl_ja4h: The HTTP fingerprint.
  • $http_ssl_ja4s: The HTTP/2 fingerprint (JA4S).
  • $http_ssl_ja4tcp: The TCP fingerprint.
  • $http_ssl_ja4one: The composite fingerprint.

Add them to your log_format:

http {
    log_format ja4_log '$remote_addr - [$time_local] "$request" '
                       'JA4="$http_ssl_ja4" JA4H="$http_ssl_ja4h" '
                       'JA4S="$http_ssl_ja4s" JA4TCP="$http_ssl_ja4tcp" JA4one="$http_ssl_ja4one"';

    access_log logs/ja4.log ja4_log;
}

2. Access Control

You can enforce rules in your server or location blocks.

Directives:

  • ja4_deny / ja4_allow: Check against JA4 (TLS) fingerprint.
  • ja4h_deny / ja4h_allow: Check against JA4H (HTTP) fingerprint.
  • ja4s_deny / ja4s_allow: Check against JA4S (HTTP/2) fingerprint.
  • ja4tcp_deny / ja4tcp_allow: Check against JA4TCP (TCP) fingerprint.
  • ja4one_deny / ja4one_allow: Check against JA4one composite fingerprint.

Example:

server {
    listen 443 ssl;
    
    # Enable JA4 module (optional, enabled implicitly by directives/variables)
    ja4 on;

    # Block a specific curl client via JA4H
    ja4h_deny "ge11n03_fe444ad14866d725a8e22320d4ed56810819b0fae0efae0c09bedfdd";
    
    # Block suspicious TCP fingerprints (example pattern)
    ja4tcp_deny "29200_2-4-8-1-3_1460_7";  # Python-like small window
    
    # Block specific HTTP/2 fingerprint (JA4S)
    ja4s_deny "h204_0000000000000000000000000000000000000000000000000000000000000000_0_xxxx_0";

    location / {
        # Allow specific bot via JA4one
        ja4one_allow "t13d3012_..._ge11n03_...";
        
        # Add headers for debugging
        add_header X-JA4-Fingerprint $http_ssl_ja4;
        add_header X-JA4H-Fingerprint $http_ssl_ja4h;
        add_header X-JA4TCP-Fingerprint $http_ssl_ja4tcp;
        add_header X-JA4one-Fingerprint $http_ssl_ja4one;
        add_header X-JA4S-Fingerprint $http_ssl_ja4s;
    }
}

Fingerprint Formats (Custom)

Due to the full hash modification, the formats are:

  • JA4: t13d1516h2_<64-char-cipher-hash>_<64-char-extension-hash>
  • JA4H: ge11n03_<64-char-header-hash>
    • ge: GET Method
    • 11: HTTP 1.1
    • n: No Cookie
    • 03: 3 Headers
  • JA4S: h2<ver>_<cnt>_<hash>_<window>_0
    • Example: h220_02_70946d4d1524_10485760_0
    • ver: Protocol version (20 = HTTP/2.0)
    • cnt: Count of settings (02 = constant in No-Patch mode)
    • hash: SHA256 of Window & Frame Size
    • window: Initial Window Size
  • JA4TCP: <window>_<option_kinds>_<mss>_<scale>
    • Example: 65535_2-4-8-3_1460_7
    • window: TCP window size (decimal)
    • option_kinds: TCP option kinds in order (decimal, dash-separated, NOPs included)
    • mss: Maximum Segment Size (decimal)
    • scale: Window scale factor (decimal)
  • JA4one: [JA4]_[JA4H] (Concatenated)

Usage Examples

Example 1: Bot Detection and Blocking

Identify and block common bot fingerprints:

server {
    listen 443 ssl;
    server_name example.com;
    
    # Block known bot JA4 fingerprints
    ja4_deny "t13d1517h2_8daaf6152771...";
    ja4_deny "t12d2917h2_7af8b3c9...";
    
    # Block bot HTTP patterns
    ja4h_deny "ge11n00_3b5f7...";
    
    location / {
        return 200 "OK";
    }
}

Example 2: Allow Only Legitimate Browsers

Create a whitelist of known good browsers:

server {
    listen 443 ssl;
    server_name secure-api.example.com;
    
    # Allow Chrome
    ja4_allow "t13d3016h2_8daaf6152771...";
    # Allow Firefox
    ja4_allow "t13d2217h2_a6bf3e8f...";
    # Allow Safari
    ja4_allow "t13d1516h2_9fe2a8c3...";
    
    # Deny all other fingerprints
    ja4_deny "all";
    
    location /api/ {
        proxy_pass http://backend;
    }
}

Example 3: Multi-Layer Fingerprinting

Combine TLS, HTTP, and TCP fingerprinting for maximum security:

server {
    listen 443 ssl;
    server_name api.example.com;
    
    # Block suspicious TCP patterns (typical of bots/scanners)
    ja4tcp_deny "1024_2-4-8_1460_0";     # Unusual small window
    ja4tcp_deny "29200_2-4-8-1-3_1460_7"; # Python requests pattern
    
    # Block known malicious JA4H patterns
    ja4h_deny "po11n00_8b3f...";
    
    location /sensitive/ {
        # Additional validation using JA4one
        ja4one_deny "t13d1515h2_..._ge10n00_...";
        
        # Add all fingerprints to response headers for monitoring
        add_header X-JA4 $http_ssl_ja4 always;
        add_header X-JA4H $http_ssl_ja4h always;
        add_header X-JA4TCP $http_ssl_ja4tcp always;
        add_header X-JA4one $http_ssl_ja4one always;
        
        proxy_pass http://sensitive-backend;
    }
}

Example 4: Analytics and Monitoring

Log all fingerprints for traffic analysis:

http {
    # Define comprehensive logging format
    log_format fingerprint_analytics '$remote_addr - $remote_user [$time_local] '
                                     '"$request" $status $body_bytes_sent '
                                     '"$http_referer" "$http_user_agent" '
                                     'JA4="$http_ssl_ja4" '
                                     'JA4H="$http_ssl_ja4h" '
                                     'JA4TCP="$http_ssl_ja4tcp" '
                                     'JA4one="$http_ssl_ja4one"';
    
    server {
        listen 443 ssl;
        server_name analytics.example.com;
        
        # Log to special file
        access_log /var/log/nginx/fingerprints.log fingerprint_analytics;
        
        location / {
            return 200 "Logged";
        }
    }
}

Example 5: Rate Limiting by Fingerprint

Use fingerprints with Lua/OpenResty for advanced rate limiting:

http {
    lua_shared_dict fingerprint_limit 10m;
    
    server {
        listen 443 ssl;
        
        location /api/ {
            access_by_lua_block {
                local limit = require "resty.limit.req"
                local fingerprint = ngx.var.http_ssl_ja4one or "unknown"
                
                -- Different limits based on fingerprint
                local lim, err = limit.new("fingerprint_limit", 10, 5)
                if not lim then
                    ngx.log(ngx.ERR, "failed to instantiate limit.req: ", err)
                    return ngx.exit(500)
                end
                
                local key = fingerprint
                local delay, err = lim:incoming(key, true)
                if not delay then
                    if err == "rejected" then
                        return ngx.exit(429)
                    end
                    return ngx.exit(500)
                end
            }
            
            proxy_pass http://backend;
        }
    }
}

Example 6: Conditional Access Based on Fingerprint Type

Different behaviors for different client types:

map $http_ssl_ja4tcp $client_type {
    default "unknown";
    "~^65535_" "modern_client";
    "~^1024_" "legacy_client";
    "~^8192_" "suspicious";
}

server {
    listen 443 ssl;
    
    location / {
        # Redirect suspicious clients to captcha
        if ($client_type = "suspicious") {
            return 302 /captcha;
        }
        
        # Limit features for legacy clients
        if ($client_type = "legacy_client") {
            return 301 /legacy-version;
        }
        
        # Full access for modern clients
        proxy_pass http://backend;
    }
    
    location /captcha {
        return 200 "Please verify you are human";
    }
}

Troubleshooting

  • Missing JA4? Ensure you are connecting via HTTPS. Plain HTTP requests have no TLS handshake, so $http_ssl_ja4 will be empty.
  • Missing JA4S? Only available for HTTP/2 connections. Check if client supports H2 and NGINX http2 is enabled.
  • Missing JA4TCP? JA4TCP requires access to raw TCP connection data. Ensure NGINX is running with --with-http_realip_module and proper network configuration. If using Docker, use --network host mode for accurate TCP fingerprinting.
  • Short Codes? If you see 12-char hashes, you are running the standard version. Recompile with the modified generic hash length if full hashes are required.

About

JA4 Nginx module is a third-party, open-source module that enables Nginx to generate JA4 fingerprints from incoming client TLS handshakes

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors