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:
- CRC32 Checksum (4 bytes) β Validates token integrity
- Unix Timestamp (8 bytes, big-endian) β Specifies when the token expires
- 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 |
PTA vs secure_link: Which Should You Use?
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.
secure_link (Built-in)
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/*
When to Use secure_link
Choose the built-in secure_link when:
- Simplicity is priority β No module installation needed
- You need variables β Access
$secure_linkfor 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
- Construct the plaintext:
timestamp (8 bytes, big-endian) + URI path - Calculate CRC32 checksum of the plaintext
- Prepend CRC32 (4 bytes, big-endian) to create:
CRC32 + timestamp + path - Apply PKCS#7 padding to align to 16-byte boundary
- Encrypt with AES-128 CBC using your key and IV
- 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:
- Current state: Primary key is active, secondary is backup
- Rotation step 1: Generate new tokens with secondary key
- Rotation step 2: Swap keys in configuration and reload NGINX
- 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.
Cookie-Based Authentication
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.

