Skip to main content

NGINX / Security

NGINX PTA Module: Time-Limited Token Authentication

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 PTA module (Period of Time Authentication) solves a critical challenge for system administrators: protecting digital content from unauthorized access and hotlinking. This module enables time-limited, encrypted access tokens that control who can access your content and for how long.

Unlike basic authentication methods that require usernames and passwords, the NGINX PTA module uses cryptographically signed tokens embedded in URLs or cookies. These tokens contain an expiration timestamp and the authorized path. This makes them ideal for temporary download links, video streaming access, and content delivery networks that need to prevent hotlinking.

How PTA Authentication Works

The NGINX PTA module operates by decrypting a token parameter (pta=...) from either the query string or a cookie. This token contains three critical pieces of information:

  1. CRC32 Checksum (4 bytes) – Validates token integrity
  2. Unix Timestamp (8 bytes, big-endian) – Specifies when the token expires
  3. URI Path – Defines which content the token authorizes access to

The entire payload is encrypted using AES-128 CBC mode with PKCS#7 padding. When a request arrives, NGINX decrypts the token, verifies the checksum, checks the expiration time, and compares the embedded path against the requested URI.

If all checks pass, access is granted. The pta parameter is then automatically stripped from the URL. If validation fails, NGINX returns an appropriate error:

Condition HTTP Response
Missing or malformed token 400 Bad Request
Wrong encryption key 403 Forbidden
Path mismatch 403 Forbidden
Expired token 410 Gone

NGINX includes a built-in secure_link module that also provides time-limited URL protection. Before installing the NGINX PTA module, consider which approach fits your needs.

The secure_link module uses HMAC signatures to validate URLs:

location /downloads/ {
    secure_link $arg_md5,$arg_expires;
    secure_link_md5 "$secure_link_expires$uri mysecretkey";

    if ($secure_link = "") {
        return 403;
    }
    if ($secure_link = "0") {
        return 410;
    }

    root /var/www/protected;
}

Token generation (shell):

EXPIRES=$(($(date +%s) + 3600))
URI="/downloads/file.zip"
SECRET="mysecretkey"
MD5=$(echo -n "${EXPIRES}${URI} ${SECRET}" | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =)
echo "https://example.com${URI}?md5=${MD5}&expires=${EXPIRES}"

Feature Comparison

Feature PTA Module secure_link (built-in)
Installation Requires module Built into NGINX
Token contents Encrypted (hidden) Signed (visible)
Expiration visible in URL No Yes (&expires=...)
Path visible in URL No (in token) Yes (normal URL path)
Error handling Automatic Requires if blocks
Key rotation Dual-key built-in Manual config change
Cookie support Built-in Requires custom logic
Variables exported None $secure_link, $secure_link_expires
Wildcard paths Yes No

When to Use PTA

Choose the NGINX PTA module when:

  • Token contents must be hidden – The encrypted token conceals both the expiry time and authorized path
  • Cookie-based auth is needed – PTA supports cookies natively with fallback logic
  • Key rotation is critical – Dual-key support enables zero-downtime rotation
  • Wildcard paths are required – A single token can authorize /videos/*

Choose the built-in secure_link when:

  • Simplicity is priority – No module installation needed
  • You need variables – Access $secure_link for conditional logic
  • Visible expiry is acceptable – Many CDN use cases don’t need to hide timestamps
  • You want HMAC – MD5/SHA-based signatures vs AES encryption

For most hotlink protection scenarios, secure_link is sufficient. Choose PTA when you specifically need encrypted tokens or its additional features. For API gateway authentication with JWT token exchange, see the Phantom Token module.

Installation

RHEL, CentOS, AlmaLinux, Rocky Linux

Install the NGINX PTA module from the GetPageSpeed repository:

sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-pta

Then load the module by adding this directive at the top of /etc/nginx/nginx.conf:

load_module modules/ngx_http_pta_module.so;

For more information about the RPM package, visit the nginx-module-pta page.

Debian and Ubuntu

First, set up the GetPageSpeed APT repository, then install:

sudo apt-get update
sudo apt-get install nginx-module-pta

On Debian/Ubuntu, the package handles module loading automatically. No load_module directive is needed.

For package details, see the APT module page.

Configuration Directives

The NGINX PTA module provides six configuration directives. These are split between server and location contexts.

Server Context Directives

These directives define encryption keys. They must be placed in the server block:

pta_1st_key

Sets the primary AES-128 encryption key.

  • Syntax: pta_1st_key <32_hex_characters>;
  • Context: server
  • Required: Yes (at least one key pair)
pta_1st_key 0102030405060708090a0b0c0d0e0f00;

The key must be exactly 32 hexadecimal characters (128 bits).

pta_1st_iv

Sets the initialization vector for the primary key.

  • Syntax: pta_1st_iv <32_hex_characters>;
  • Context: server
  • Required: Yes (paired with pta_1st_key)
pta_1st_iv 00000000000000000000000000000000;

pta_2nd_key

Sets the secondary AES-128 encryption key for key rotation.

  • Syntax: pta_2nd_key <32_hex_characters>;
  • Context: server
  • Required: No
pta_2nd_key 11111111111111111111111111111111;

pta_2nd_iv

Sets the initialization vector for the secondary key.

  • Syntax: pta_2nd_iv <32_hex_characters>;
  • Context: server
  • Required: No (paired with pta_2nd_key)
pta_2nd_iv 22222222222222222222222222222222;

Location Context Directives

These directives control PTA behavior for specific locations:

pta_enable

Enables or disables PTA authentication for the location.

  • Syntax: pta_enable on | off;
  • Default: off
  • Context: location
location /protected/ {
    pta_enable on;
}

pta_auth_method

Specifies where to look for the PTA token.

  • Syntax: pta_auth_method qs | cookie | qs cookie;
  • Default: qs
  • Context: location
Value Behavior
qs Token from query string only (?pta=...)
cookie Token from cookie only (Cookie: pta=...)
qs cookie Try query string first, fall back to cookie if absent
location /videos/ {
    pta_enable on;
    pta_auth_method qs cookie;
}

Complete Configuration Example

Here is a production-ready configuration. It protects multiple content locations:

load_module modules/ngx_http_pta_module.so;

http {
    server {
        listen 80;
        server_name example.com;

        # Primary and secondary encryption keys for key rotation
        pta_1st_key a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6;
        pta_1st_iv  00112233445566778899aabbccddeeff;
        pta_2nd_key d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1;
        pta_2nd_iv  ffeeddccbbaa99887766554433221100;

        # Public content - no authentication required
        location / {
            root /var/www/html;
        }

        # Protected downloads - query string authentication
        location /downloads/ {
            pta_enable on;
            pta_auth_method qs;
            root /var/www/protected;
        }

        # Video streaming - cookie or query string
        location /videos/ {
            pta_enable on;
            pta_auth_method qs cookie;
            root /var/www/media;
        }
    }
}

Test the configuration:

nginx -t
systemctl reload nginx

Generating PTA Tokens

Tokens must be generated server-side using your application’s programming language. The module’s GitHub repository provides reference implementations in Go, Java, and Perl.

Token Generation Algorithm

  1. Construct the plaintext: timestamp (8 bytes, big-endian) + URI path
  2. Calculate CRC32 checksum of the plaintext
  3. Prepend CRC32 (4 bytes, big-endian) to create: CRC32 + timestamp + path
  4. Apply PKCS#7 padding to align to 16-byte boundary
  5. Encrypt with AES-128 CBC using your key and IV
  6. Hex-encode the ciphertext

Python Token Generator

Here is a practical Python implementation:

import struct
import binascii
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

def generate_pta_token(key_hex, iv_hex, url_path, expiry_timestamp):
    """Generate a PTA token for NGINX authentication.

    Args:
        key_hex: 32 hex characters (128-bit AES key)
        iv_hex: 32 hex characters (initialization vector)
        url_path: Path to authorize (e.g., '/downloads/file.zip')
        expiry_timestamp: Unix timestamp when token expires

    Returns:
        Hex-encoded encrypted token string
    """
    key = binascii.unhexlify(key_hex)
    iv = binascii.unhexlify(iv_hex)

    # Build plaintext: timestamp (8 bytes big-endian) + path
    timestamp_bytes = struct.pack(">Q", expiry_timestamp)
    path_bytes = url_path.encode("utf-8")

    # Calculate CRC32 of (timestamp + path)
    crc_data = timestamp_bytes + path_bytes
    crc = binascii.crc32(crc_data) & 0xffffffff
    crc_bytes = struct.pack(">I", crc)

    # Full plaintext: CRC32 (4 bytes) + timestamp (8 bytes) + path
    plaintext = crc_bytes + timestamp_bytes + path_bytes

    # PKCS#7 padding to 16-byte boundary
    padding_len = 16 - (len(plaintext) % 16)
    plaintext += bytes([padding_len] * padding_len)

    # AES-128-CBC encryption
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(plaintext) + encryptor.finalize()

    return ciphertext.hex()

Example usage:

import time

key = "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
iv = "00112233445566778899aabbccddeeff"
path = "/downloads/software.zip"
expires = int(time.time()) + 3600  # Valid for 1 hour

token = generate_pta_token(key, iv, path, expires)
download_url = f"https://example.com{path}?pta={token}"

Wildcard Path Matching

The NGINX PTA module supports wildcard patterns in the authorized path. This allows a single token to grant access to multiple files.

Trailing Wildcard

A trailing asterisk matches any suffix:

# Token for /videos/* matches:
# - /videos/intro.mp4
# - /videos/chapter1.mp4
# - /videos/bonus/extra.mp4

token = generate_pta_token(key, iv, "/videos/*", expires)

Middle Wildcard

An asterisk in the middle matches any single path segment:

# Token for /users/*/avatar.jpg matches:
# - /users/john/avatar.jpg
# - /users/jane/avatar.jpg

token = generate_pta_token(key, iv, "/users/*/avatar.jpg", expires)

Only one wildcard is permitted per path. To use a literal asterisk, escape it with a backslash (\*).

Key Rotation for Zero-Downtime Updates

The dual key pair configuration enables seamless key rotation. There is no service interruption:

  1. Current state: Primary key is active, secondary is backup
  2. Rotation step 1: Generate new tokens with secondary key
  3. Rotation step 2: Swap keys in configuration and reload NGINX
  4. Rotation step 3: Old tokens continue working until they expire

The module automatically tries both keys during decryption. It accepts tokens encrypted with either one. This eliminates the need to coordinate token generation with configuration updates.

For video players and applications where query strings are problematic, cookie-based authentication provides a cleaner alternative:

location /stream/ {
    pta_enable on;
    pta_auth_method cookie;
    root /var/www/media;
}

Set the cookie from your application before redirecting:

# Set cookie with 1-hour expiry
response.set_cookie(
    'pta',
    token,
    httponly=True,
    secure=True,
    samesite='Strict',
    max_age=3600
)

The browser automatically includes the cookie in subsequent requests.

Performance Considerations

The NGINX PTA module adds minimal overhead to request processing:

  • Cryptographic operations: AES-128 decryption is hardware-accelerated via AES-NI
  • Memory usage: Temporary buffers are allocated and released per request
  • CPU impact: Negligible for typical workloads; suitable for high-traffic CDNs

The module hooks into NGINX’s rewrite phase. It executes before content handlers. Invalid tokens are rejected early, preventing unnecessary filesystem or upstream operations.

Security Best Practices

For comprehensive NGINX security, combine the NGINX PTA module with other protective measures. Consider IP blocking with honeypots for automated threat response.

Generate Strong Keys

Use cryptographically secure random number generators:

# Generate 128-bit key (32 hex chars)
openssl rand -hex 16

# Generate IV
openssl rand -hex 16

Never use predictable values like all zeros in production.

Set Appropriate Expiration Times

Balance security with usability:

Use Case Recommended Expiry
One-time downloads 5-15 minutes
Video streaming session 2-4 hours
Daily access links 24 hours

Shorter expiration times limit the window for token theft.

Protect Token Generation

The encryption keys must remain confidential:

  • Store keys in environment variables or secret management systems
  • Never expose keys in client-side code or public repositories
  • Use different keys for each environment

Monitor for Abuse

Enable logging to detect unusual access patterns. For more advanced logging needs, see the conditional logging module.

log_format pta_access '$remote_addr [$time_local] "$request" '
                      '$status $body_bytes_sent';

location /protected/ {
    pta_enable on;
    access_log /var/log/nginx/pta-access.log pta_access;
}

Watch for high rates of 400, 403, or 410 responses.

Troubleshooting

Token Always Returns 400 Bad Request

Cause: Missing pta parameter or malformed hex string.

Check:
– Verify the token is hex-encoded (only characters 0-9, a-f, A-F)
– Confirm the parameter name is exactly pta (lowercase)
– Ensure the token length is even (hex requires pairs)

Token Returns 403 Forbidden

Cause: Decryption succeeded but path validation failed.

Check:
– Verify the path in the token matches the request URI exactly
– Confirm the key/IV in NGINX matches the token generator
– Check if wildcards are used correctly (only one allowed)

Token Returns 410 Gone

Cause: Token has expired.

Check:
– Verify system clocks are synchronized
– Confirm the expiration timestamp is in the future
– Remember timestamps are Unix epoch (seconds since 1970-01-01)

Debugging with Error Logs

The module logs validation failures at the error level:

tail -f /var/log/nginx/error.log | grep pta

Common log messages:

pta token is invalid #1  # Missing pta parameter
pta token is invalid #2  # Odd-length hex string
request is expired       # Token past expiration
url is invalid          # Path mismatch
decrypt failed          # Wrong key or corrupted token

Conclusion

The NGINX PTA module provides content protection through time-limited encrypted tokens. Its key advantage over the built-in secure_link module is token encryption – the expiry time and authorized path remain hidden from users.

Choose PTA when you need:

  • Hidden token contents – Encrypted vs signed
  • Cookie authentication – Native support with fallback
  • Key rotation – Dual-key architecture
  • Wildcard paths – Single token for multiple files

For simpler hotlink protection where visible expiry timestamps are acceptable, the built-in secure_link module may be sufficient.

For source code and additional examples, visit the PTA module on GitHub.

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.