yum upgrades for production use, this is the repository for you.
Active subscription is required.
Passing sensitive data through cookies or URL parameters exposes it to tampering and eavesdropping. Even over HTTPS, clients can read and modify their own cookie values. The NGINX encrypted session module solves this by encrypting and decrypting variable values directly inside NGINX configuration using AES-256-CBC. This lets you create tamper-proof, time-limited session tokens without writing application code.
In this guide, you will learn how to install and configure the NGINX encrypted session module. You will build encrypted cookies for session management, create self-expiring tokens, and protect sensitive data passed between NGINX locations. Every configuration example has been tested on a clean Rocky Linux 10 server.
Why Use the NGINX Encrypted Session Module
The encrypted session module brings server-side encryption to NGINX’s configuration layer. Here is why that matters:
- Tamper-proof tokens. Encrypted values cannot be modified by clients. Any alteration breaks decryption, and NGINX silently rejects the token. There is no need for application-level validation.
- Built-in expiration. Every encrypted value embeds an expiration timestamp. NGINX automatically rejects expired tokens during decryption. You do not need external session stores or database lookups.
- No application changes required. The encrypted session module operates entirely within NGINX configuration. Your backend receives decrypted values as regular variables. PHP, Python, Node.js, and Java applications work without modification.
- Lightweight session management. For simple use cases like user identification or feature flags, you can avoid Redis, Memcached, or database-backed sessions entirely. The NGINX encrypted session module handles the encryption and expiration logic.
- Defense in depth. Even if an attacker intercepts an encrypted cookie, they cannot read or forge its contents without the server’s secret key.
How the NGINX Encrypted Session Module Works
The module uses AES-256-CBC encryption with an MD5 message authentication code (MAC). Here is what happens during encryption and decryption:
Encryption Process
- The module takes a plaintext value and the configured expiration time.
- It appends an expiration timestamp (current time plus the configured
encrypted_session_expiresvalue) to the plaintext data. - It computes an MD5 digest of the combined data (plaintext + timestamp) for integrity verification.
- It encrypts the combined data using AES-256-CBC with the configured key and initialization vector (IV).
- The final output is:
MD5_digest + AES_encrypted_data(binary).
Decryption Process
- The module extracts the MD5 digest from the beginning of the input.
- It decrypts the remaining data using AES-256-CBC with the same key and IV.
- It computes a new MD5 digest of the decrypted data and compares it with the extracted digest. If they differ, decryption fails (data was tampered with).
- It extracts the expiration timestamp from the decrypted data. If the current time exceeds the expiration time, decryption fails (token expired).
- On success, the module returns the original plaintext value.
Because the output is binary, you typically encode it with Base64 or Base32 before storing it in cookies or URL parameters. The NGINX set-misc module provides the set_encode_base64 and set_decode_base64 directives for this purpose.
Encrypted Session Module vs. NGINX Secure Link
NGINX includes a built-in secure_link module that also creates time-limited, tamper-proof URLs. However, the encrypted session module and secure_link serve different purposes:
| Feature | Encrypted Session Module | Secure Link (built-in) |
|---|---|---|
| Algorithm | AES-256-CBC encryption | MD5 hash verification |
| Data protection | Confidentiality + integrity | Integrity only |
| Direction | Two-way (encrypt and decrypt) | One-way (hash and verify) |
| Payload | Arbitrary data of any length | No payload — hash only |
| Expiration | Embedded in encrypted data | Plaintext timestamp in URL |
| Use case | Session tokens, encrypted cookies | Signed download links |
Key difference: The secure_link module verifies that a URL has not been tampered with, but clients can see the parameters in plaintext. The NGINX encrypted session module hides the data entirely — clients see only an opaque encrypted blob.
Use secure_link when you need signed, time-limited download URLs where the parameters (like file path) can be visible. Use the encrypted session module when you need to store sensitive data (like user IDs or roles) in cookies or URL parameters where clients must not see or modify the contents.
Installation
The NGINX encrypted session module requires two dependencies: the NDK (NGINX Development Kit) and OpenSSL. Both are handled automatically by the GetPageSpeed package.
RHEL, CentOS, AlmaLinux, Rocky Linux
sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-encrypted-session
The nginx-module-ndk dependency is installed automatically.
Then add the following lines at the top of /etc/nginx/nginx.conf, before any http, events, or other blocks:
load_module modules/ndk_http_module.so;
load_module modules/ngx_http_encrypted_session_module.so;
Important: The NDK module must be loaded before the encrypted session module. The order of load_module directives matters.
You will also want the set-misc module for Base64 encoding and decoding:
sudo dnf install nginx-module-set-misc
Then add its load_module directive after the NDK line:
load_module modules/ndk_http_module.so;
load_module modules/ngx_http_encrypted_session_module.so;
load_module modules/ngx_http_set_misc_module.so;
Debian and Ubuntu
First, set up the GetPageSpeed APT repository, then install:
sudo apt-get update
sudo apt-get install nginx-module-encrypted-session nginx-module-set-misc
On Debian/Ubuntu, the package handles module loading automatically. No
load_moduledirective is needed.
After installation on any platform, verify that NGINX accepts the configuration:
sudo nginx -t
Configuration Reference
The NGINX encrypted session module provides five directives. Three configure encryption parameters, and two perform the actual encryption and decryption.
encrypted_session_key
Syntax: encrypted_session_key <key>;
Default: —
Context: http, server, server if, location, location if
Sets the AES-256 encryption key. The key must be exactly 32 bytes long (256 bits). This is the most critical security parameter — treat it like a password.
encrypted_session_key "abcdefghijklmnopqrstuvwxyz123456";
If the key is not exactly 32 bytes, NGINX will refuse to start:
nginx: [emerg] encrypted_session_key: the key must be of 32 bytes long
encrypted_session_iv
Syntax: encrypted_session_iv <iv>;
Default: encrypted_session_iv "deadbeefdeadbeef";
Context: http, server, server if, location, location if
Sets the initialization vector (IV) for AES-256-CBC. The IV must be no longer than 16 bytes. If shorter than 16 bytes, the remaining bytes are zero-padded.
encrypted_session_iv "1234567812345678";
The default IV is deadbeefdeadbeef. For production use, always set a custom IV. Using the default makes your encryption predictable if an attacker knows you use the NGINX encrypted session module.
encrypted_session_expires
Syntax: encrypted_session_expires <time>;
Default: encrypted_session_expires 1d;
Context: http, server, server if, location, location if
Sets the token expiration time. The value uses NGINX time notation: s for seconds, m for minutes, h for hours, d for days. Plain numbers are treated as seconds.
encrypted_session_expires 3600; # 1 hour
encrypted_session_expires 30m; # 30 minutes
encrypted_session_expires 7d; # 7 days
encrypted_session_expires 0; # never expires
The expiration time is embedded into the encrypted data at encryption time. During decryption, NGINX compares the embedded expiration timestamp with the current server time. If the token has expired, decryption returns an empty string.
This is not an HTTP cookie expiration. Even if a cookie’s Max-Age or Expires header allows longer lifetimes, the encrypted session module will reject the token once the embedded expiration passes. This server-side enforcement cannot be bypassed by the client.
set_encrypt_session
Syntax: set_encrypt_session $variable <value>;
set_encrypt_session $variable;
Context: http, server, server if, location, location if
Encrypts the given value and stores the binary result in $variable. If only one argument is provided, the variable is encrypted in place.
set $raw "user_id=42";
set_encrypt_session $encrypted $raw;
The output is binary data. You must encode it before using it in cookies or URLs:
set_encrypt_session $encrypted $raw;
set_encode_base64 $token $encrypted; # from set-misc module
set_decrypt_session
Syntax: set_decrypt_session $variable <value>;
set_decrypt_session $variable;
Context: http, server, server if, location, location if
Decrypts the given value and stores the plaintext result in $variable. Returns an empty string if decryption fails for any reason: wrong key, tampered data, or expired token.
set_decode_base64 $decoded $cookie_session; # from set-misc module
set_decrypt_session $plaintext $decoded;
if ($plaintext = "") {
return 403;
}
Configuration Examples
Encrypted Cookie Authentication
This is the most common use case for the NGINX encrypted session module. NGINX encrypts a user identifier into a cookie at login time, then decrypts it on subsequent requests to verify identity:
encrypted_session_key "abcdefghijklmnopqrstuvwxyz123456";
encrypted_session_iv "1234567812345678";
encrypted_session_expires 1h;
server {
listen 443 ssl;
server_name example.com;
# Login: encrypt user ID into a cookie
location /login {
set $uid $arg_user;
set_encrypt_session $session $uid;
set_encode_base64 $cookie_val $session;
add_header Set-Cookie "session=$cookie_val; Path=/; HttpOnly; Secure";
return 200 "Logged in as $uid\n";
}
# Protected area: decrypt cookie to verify identity
location /protected {
set_decode_base64 $session $cookie_session;
set_decrypt_session $uid $session;
if ($uid = "") {
return 401 "Unauthorized\n";
}
return 200 "Welcome, $uid!\n";
}
}
For additional cookie security, combine this with the NGINX cookie flag module to enforce HttpOnly, Secure, and SameSite attributes.
Time-Limited Download Tokens
Generate encrypted tokens that grant temporary access to protected resources. The NGINX encrypted session module embeds the expiration directly in the token:
encrypted_session_key "abcdefghijklmnopqrstuvwxyz123456";
encrypted_session_iv "1234567812345678";
encrypted_session_expires 30m;
server {
listen 443 ssl;
server_name cdn.example.com;
# Generate a download token
location /generate-token {
set_encrypt_session $encrypted "/files/report.pdf";
set_encode_base64 $token $encrypted;
return 200 "$token\n";
}
# Validate token and serve file
location /download {
set_unescape_uri $token $arg_token;
set_decode_base64 $decoded $token;
set_decrypt_session $file_path $decoded;
if ($file_path = "") {
return 403 "Token expired or invalid\n";
}
# Serve the file
root /var/www;
try_files $file_path =404;
}
}
After 30 minutes, the token automatically expires and NGINX rejects it — even if someone saved the URL.
Passing Encrypted Data to Backends
Decrypt session data in NGINX and pass the plaintext to your application backend as a header or query parameter:
encrypted_session_key "abcdefghijklmnopqrstuvwxyz123456";
encrypted_session_iv "1234567812345678";
encrypted_session_expires 1d;
server {
listen 443 ssl;
server_name app.example.com;
location / {
set_decode_base64 $session $cookie_session;
set_decrypt_session $user_data $session;
# Pass decrypted data to backend
proxy_set_header X-User-Data $user_data;
proxy_pass http://backend;
}
location ~ \.php$ {
set_decode_base64 $session $cookie_session;
set_decrypt_session $user_data $session;
# Pass decrypted data to PHP-FPM
fastcgi_param HTTP_X_USER_DATA $user_data;
fastcgi_pass unix:/run/php-fpm/www.sock;
include fastcgi_params;
}
}
Your backend receives the decrypted value in the X-User-Data header. If the cookie is missing, tampered, or expired, the header will be empty.
Short-Lived CSRF Tokens
Create encrypted CSRF tokens with a short lifespan. The encrypted session module embeds the expiration directly in the token:
encrypted_session_key "abcdefghijklmnopqrstuvwxyz123456";
encrypted_session_iv "1234567812345678";
server {
listen 443 ssl;
server_name example.com;
# Generate a CSRF token valid for 10 minutes
location /csrf-token {
encrypted_session_expires 10m;
set_encrypt_session $encrypted "csrf_valid";
set_encode_base64 $token $encrypted;
return 200 "$token";
}
# Validate the CSRF token on form submission
location /submit {
set_unescape_uri $token $arg_csrf;
set_decode_base64 $decoded $token;
set_decrypt_session $csrf_check $decoded;
if ($csrf_check = "") {
return 403 "Invalid or expired CSRF token\n";
}
proxy_pass http://backend;
}
}
Testing and Verification
After configuring the encrypted session module, verify the configuration syntax:
sudo nginx -t
Then reload NGINX:
sudo systemctl reload nginx
Verify the Encrypt-Decrypt Round Trip
Test that encryption and decryption produce the expected result:
# Encrypt a value
TOKEN=$(curl -s http://localhost/encrypt)
echo "Encrypted token: $TOKEN"
# Decrypt it back
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$TOKEN'))")
curl -s "http://localhost/decrypt?token=$ENCODED"
You should see the original plaintext value in the decrypted output.
Verify Token Expiration
Create a short-lived token and confirm it expires:
# Get a token with 2-second expiry
TOKEN=$(curl -s http://localhost/short-lived)
# Immediately valid
curl -s "http://localhost/check-expiry?token=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$TOKEN'))")"
# Output: Still valid
# Wait for expiry
sleep 3
# Now expired
curl -s "http://localhost/check-expiry?token=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$TOKEN'))")"
# Output: Token expired or invalid
Verify Tamper Resistance
Confirm that modified tokens are rejected:
# Send a fake/tampered base64 string
curl -s "http://localhost/decrypt?token=dGFtcGVyZWRfZGF0YV9oZXJl"
# Output: Invalid or expired token
Verify Cookie-Based Authentication
Test the full login and protected resource flow:
# Login and capture the cookie
curl -sI "http://localhost/login?user=danila" | grep Set-Cookie
# Use the cookie to access the protected resource
COOKIE=$(curl -sI "http://localhost/login?user=danila" | grep -o 'session=[^;]*' | head -1)
curl -s -b "$COOKIE" http://localhost/protected
# Output: Welcome, danila!
# Try with a fake cookie
curl -s -b "session=ZmFrZV9jb29raWU=" http://localhost/protected
# Output: Unauthorized
Security Best Practices
Generate a Strong Encryption Key
Do not use obvious strings like abcdefghijklmnopqrstuvwxyz123456 in production. Generate a cryptographically random 32-byte key:
openssl rand -base64 32 | head -c 32
Store the key securely and restrict access to the NGINX configuration file:
sudo chmod 640 /etc/nginx/nginx.conf
Use a Custom Initialization Vector
The default IV (deadbeefdeadbeef) is publicly known. Always configure a custom IV in production:
openssl rand -hex 8
Use the output as your encrypted_session_iv value.
Set Appropriate Expiration Times
Choose expiration times that balance security with usability:
| Use case | Recommended expiry |
|---|---|
| Session cookies | 1h to 8h |
| Remember-me tokens | 7d to 30d |
| Download links | 5m to 1h |
| CSRF tokens | 10m to 30m |
| One-time actions | 60 (seconds) |
Shorter expiration times reduce the window of opportunity if a token is stolen.
Combine with Secure Cookie Attributes
Always set HttpOnly, Secure, and SameSite attributes on encrypted cookies. Use the cookie flag module or set them manually in the add_header directive:
add_header Set-Cookie "session=$cookie_val; Path=/; HttpOnly; Secure; SameSite=Lax";
Protect the Key During Rotation
When rotating encryption keys, you must handle the transition carefully. Tokens encrypted with the old key will fail decryption with the new key. Strategies include:
- Graceful degradation: When decryption fails, redirect the user to re-authenticate rather than showing an error.
- Dual-key period: Temporarily try decryption with both old and new keys (requires application logic or Lua).
- Coordinated rotation: Change the key and set
encrypted_session_expires 0briefly to invalidate all existing tokens.
Understand the Cryptographic Limitations
The NGINX encrypted session module uses AES-256-CBC with MD5 for integrity checking. While AES-256 is strong encryption, MD5 is considered cryptographically weak for collision resistance. However, in this context, MD5 serves as a checksum for detecting accidental corruption or naive tampering — not as a defense against sophisticated cryptographic attacks. For most web session use cases, this provides adequate security.
For applications requiring the highest cryptographic standards (financial transactions, medical records), consider JWT-based authentication with RS256 or ES256 signatures instead.
Performance Considerations
The NGINX encrypted session module has minimal performance impact:
- AES-256 is hardware-accelerated on modern CPUs with AES-NI instructions. Rocky Linux and other RHEL-based distributions enable this by default.
- Encryption happens once per request — only when
set_encrypt_sessionorset_decrypt_sessiondirectives execute. - No external dependencies at runtime. The module uses OpenSSL’s EVP interface directly. There are no network calls, no database lookups, and no file I/O.
- Reusable cipher context. The module allocates one
EVP_CIPHER_CTXper worker process and reuses it across requests, avoiding repeated allocation overhead.
For high-traffic sites, the encrypted session module is significantly faster than Redis-backed or database-backed session stores. The trade-off is that session data must fit in a cookie or URL parameter (typically under 4KB).
Troubleshooting
“encrypted_session_key: the key must be of 32 bytes long”
Your key is not exactly 32 characters. Count the characters carefully — spaces and quotes are not part of the key:
# Wrong — 16 bytes, not 32
encrypted_session_key "1234567890123456";
# Correct — exactly 32 bytes
encrypted_session_key "abcdefghijklmnopqrstuvwxyz123456";
“encrypted_session_iv: the init vector must NOT be longer than 16 bytes”
Your IV exceeds 16 characters:
# Wrong — 20 bytes
encrypted_session_iv "12345678901234567890";
# Correct — 16 bytes or fewer
encrypted_session_iv "1234567812345678";
Decryption Returns Empty String
An empty result from set_decrypt_session means one of:
- Token expired: The embedded expiration time has passed.
- Wrong key or IV: The decryption key or IV does not match the one used during encryption.
- Tampered data: The encrypted data was modified in transit.
- Encoding mismatch: You used
set_encode_base64for encryption but forgotset_decode_base64before decryption, or vice versa.
Enable NGINX debug logging to see detailed failure reasons:
error_log /var/log/nginx/error.log debug;
Then look for messages like:
failed to decrypt session: bad AES-256 digest
encrypted_session: session expired
“unknown directive” Errors
If NGINX reports unknown directive "set_encrypt_session", the module is not loaded. Verify:
- The module file exists:
ls /usr/lib64/nginx/modules/ngx_http_encrypted_session_module.so - The
load_moduledirectives are present and in the correct order (NDK first). - Run
nginx -tto check for loading errors.
Module Not Working After Key Change
Changing the encryption key invalidates all existing tokens. Users with cookies encrypted using the old key will get empty decryption results. To minimize disruption, change the key during low-traffic periods and expect users to need to re-authenticate.
Conclusion
The NGINX encrypted session module provides AES-256-CBC encryption for NGINX variables. It creates tamper-proof, self-expiring tokens without application code changes. Combined with the set-misc module for Base64 encoding, it handles encrypted cookies, time-limited download tokens, and secure data passing between NGINX locations.
For simple session management where you need to store a user identifier or a small piece of state, the NGINX encrypted session module eliminates the need for external session stores. For more complex authentication requirements, consider combining it with JWT authentication or time-limited token authentication (PTA).
Install the NGINX encrypted session module from the GetPageSpeed repository for RPM-based or APT-based systems, configure your key, IV, and expiration, and your NGINX server gains built-in encryption capabilities.
