The NGINX length hiding module is often recommended as a mitigation for the BREACH compression attack. However, security research has demonstrated that random padding does NOT prevent BREACH—it only slows down the attack slightly. This article explains how the module works, why it’s insufficient, and what actually protects against BREACH in 2026.
Important Disclosure: Random length padding has been proven ineffective at preventing BREACH attacks. The module at most slows down attackers, who can bypass the randomization through statistical analysis. Modern browser protections (SameSite cookies) and application-level mitigations (CSRF token rotation) provide far better protection.
The BREACH (Browser Reconnaissance and Exfiltration via Adaptive Compression of Hypertext) attack was discovered in 2013. It is tracked as CVE-2013-3587. This attack allows attackers to extract sensitive data like CSRF tokens, session identifiers, and API keys from compressed HTTPS responses by observing subtle changes in response sizes to guess secrets one character at a time.
Understanding the BREACH Attack
BREACH exploits a fundamental property of compression algorithms like gzip and deflate. When data contains repeated sequences, the compressed output becomes smaller. An attacker who can inject content into a compressed response containing secrets can systematically guess those secrets by observing size changes.
For example, if an HTML page contains a CSRF token csrf_token=abc123, an attacker might inject csrf_token=a through a form field or URL parameter. If this injection causes the compressed response to become smaller, the attacker knows the token starts with “a”. Repeating this process reveals the entire secret.
Conditions for BREACH Vulnerability
Your application is vulnerable to BREACH when all three conditions are present simultaneously:
- HTTP compression is enabled (gzip or deflate in
Accept-Encoding) - User input is reflected in responses (search terms, form values, URL parameters)
- Secrets appear in the response body (CSRF tokens, API keys, session data)
Why Length Hiding Does NOT Prevent BREACH
The theory behind length hiding is that adding random-length padding makes it impossible for attackers to correlate size changes with their guesses. However, this has been proven wrong.
From the original BREACH paper:
“While this measure does make the attack take longer, it does so only slightly… By repeating requests and averaging the sizes of the corresponding responses, the attacker can quickly learn the true length of the cipher text.”
The nginx issue tracker confirms this:
“It was demonstrated more than once that padding cannot prevent the BREACH attack. The random padding at most slows down the attack.”
The Statistical Bypass
The bypass works because of basic statistics. Random padding adds noise, but noise can be filtered out by making more requests and averaging the results. The standard error of the mean is inversely proportional to √N—attackers simply make more requests to filter out the random padding noise.
If an attacker needs 100 requests to guess a character without padding, they might need 1,000-10,000 requests with padding. This is slower but still completely feasible for an automated attack.
What Actually Protects Against BREACH
According to Qualys and security researchers, these mitigations actually work:
1. SameSite Cookies (Most Important)
BREACH requires a cross-site request forgery (CSRF) vector—the attacker must trick the victim’s browser into making requests to the target site with cookies attached. SameSite cookies break this attack vector entirely.
- Chrome, Edge, and Opera now default to
SameSite=Lax - This prevents cross-site POST requests from including cookies
- Most users on these browsers are already protected
However: Firefox and Safari have not adopted this default. Explicitly set SameSite on your cookies:
# In your application or via NGINX headers
add_header Set-Cookie "session=value; SameSite=Lax; Secure; HttpOnly";
2. CSRF Token Randomization Per Request
Modern frameworks like Django and Rails now randomize CSRF tokens on every request. Since the target secret changes constantly, BREACH cannot extract it character by character.
If your application uses static CSRF tokens, update your framework or implement per-request token rotation.
3. Disable Compression for Sensitive Responses
The most bulletproof solution is disabling compression for responses containing both user input and secrets:
location /api/sensitive {
gzip off;
proxy_pass http://backend;
}
This has a bandwidth cost but eliminates the vulnerability entirely.
4. Separate Secrets from User Input
Architect your application so secrets never appear in the same response as user-controlled content:
- Return CSRF tokens via separate API calls
- Use HttpOnly cookies for session identifiers (cookies are not in the response body)
- Deliver sensitive data through JavaScript-only endpoints that don’t reflect user input
The Length Hiding Module: When It Might Still Be Useful
Despite its limitations, the length hiding module is not entirely useless. It provides defense in depth as one layer among many. If you’ve already implemented SameSite cookies and CSRF token rotation, length hiding adds marginal additional protection by increasing the attacker’s workload.
How the Module Works
The module appends a randomly generated HTML comment to response bodies:
<!-- random-length HTML comment: JnSLGWeWYWsoJ4dXS3ubLw3YOu3zfGTotlzx7UJUo26xuXICQ2cbpVy1Dprgv8Icj6QfOZx2Ptp9HxCVoevTxhKzMzV6xeYXao0oCngRWJRb4Tvive1iBAXLzrHlLg6jKwNKXrct0tJuA2TvWIRVIng6UoffIbCQLPbi63PwmWemOxVi6m3CPa6hCbAK2CaBR1jLux7UJa4WNN4H0yIDMElMglWWouY5m5FUqAn0afMmtErj0zkA2LMWxisZRES38XLoYycySmaBrIih5IixUsJFR0ei4uZ0IifgV5SnitoNzMusSQem9npObHuU2HKApneAjwnFdPSQZA9sRdSOE8agDI05P832mV1JIcOjsg0FgzxvSG7UEX0HdqBqp2jPOYYW0k5gGtmkiXWydRJfn9lGomxReUeqq2Aec69gplEM6a8aqH5TFgXrGK8jcaPISQlsKkMxJQ7Fp6fVDbmI59xCIvlk -->
The random string varies from 1 to 2048 bytes (configurable). This forces attackers to make more requests to average out the noise, but does not prevent the attack.
Installation
If you still want to use the module as part of a defense-in-depth strategy, install it from the GetPageSpeed repository on RHEL-based systems:
sudo dnf install nginx-module-length-hiding
This works on Rocky Linux, AlmaLinux, CentOS, Fedora, and other RHEL-compatible distributions.
Load the module by adding this line at the top of your /etc/nginx/nginx.conf, before the events block:
load_module modules/ngx_http_length_hiding_filter_module.so;
Verify the module loads correctly:
nginx -t
Configuration Directives
length_hiding
Enables or disables the feature for responses.
Syntax: length_hiding on | off;
Default: off
Context: http, server, location, if in location
length_hiding_max
Sets the maximum length of the random string inside the HTML comment.
Syntax: length_hiding_max size;
Default: 2048
Context: http, server, location
Valid range: 256 to 2048 bytes
length_hiding_types
Specifies which MIME types receive the treatment.
Syntax: length_hiding_types mime-type ...;
Default: text/html
Context: http, server, location
Note: The module appends HTML comments. Applying it to non-HTML formats like JSON may produce technically invalid output.
Configuration Example
server {
listen 443 ssl;
server_name example.com;
# Enable compression - see our gzip guide for details
gzip on;
gzip_types text/html text/css application/javascript;
# Length hiding as defense-in-depth (not primary protection)
length_hiding on;
location / {
root /var/www/html;
}
# Disable compression entirely for highly sensitive endpoints
location /api/tokens {
gzip off;
length_hiding off;
proxy_pass http://backend;
}
}
For optimal compression settings, see our guide on NGINX gzip compression.
Verifying the Module Works
Make multiple requests and observe varying response sizes:
for i in {1..5}; do
curl -s -o /dev/null -w "Request $i: %{size_download} bytes\n" \
-H "Accept-Encoding: gzip" \
https://example.com/protected-page
done
With length hiding enabled, response sizes vary:
Request 1: 597 bytes
Request 2: 701 bytes
Request 3: 489 bytes
Request 4: 823 bytes
Request 5: 556 bytes
Remember: this variation slows attackers but does not stop them.
Recommended Security Strategy
Here’s the priority order for protecting against BREACH:
| Priority | Mitigation | Effectiveness |
|---|---|---|
| 1 | Set SameSite=Lax or SameSite=Strict on cookies |
Breaks the CSRF attack vector entirely |
| 2 | Rotate CSRF tokens per request | Target changes constantly, cannot be guessed |
| 3 | Separate secrets from user input | No secrets to extract |
| 4 | Disable compression for sensitive responses | Eliminates the vulnerability |
| 5 | Length hiding (this module) | Slows down attacks only |
Do not rely on length hiding as your primary or only BREACH protection.
Troubleshooting
Module Not Loading
If nginx -t fails after adding the load_module directive, verify:
- The module file exists at
/usr/lib64/nginx/modules/ngx_http_length_hiding_filter_module.so - The module version matches your NGINX version
- The
load_moduledirective appears before theeventsblock
Length Hiding Not Working
If responses do not include the random comment:
- Verify
length_hiding on;is set in the correct context - Check that the response MIME type matches
length_hiding_types - Ensure the response is not a HEAD request or 204 No Content
- Verify the request is not a subrequest
Alternative Compression Options
While this module works with gzip, you might also consider modern compression algorithms:
- Brotli compression for NGINX offers 15-25% better compression
- Zstd compression for NGINX provides fast decompression
Conclusion
The NGINX length hiding module adds random-length HTML comments to responses, which increases the number of requests an attacker needs to execute a BREACH attack. However, it does not prevent the attack—statistical analysis allows attackers to filter out the random noise.
For real protection against BREACH:
- Set SameSite cookies to break the CSRF vector
- Rotate CSRF tokens per request so the target constantly changes
- Disable compression for responses containing both secrets and user input
Use length hiding only as an additional layer in a defense-in-depth strategy, never as your primary protection.
The module is maintained by Nulab and available under the MIT license. View the source code on GitHub.

