Site icon GetPageSpeed

NGINX Cookie Flag Module: Set HttpOnly, Secure, and SameSite

NGINX Cookie Flag Module: Set HttpOnly, Secure, and SameSite

Your web application might set all the right cookies. But if those cookies lack HttpOnly, Secure, and SameSite flags, attackers can steal session tokens through cross-site scripting (XSS). They can also intercept cookies over unencrypted connections. The NGINX cookie flag module solves this problem at the reverse proxy layer. It enforces cookie security flags without modifying application code.

In this guide, you will learn how to install and configure the set_cookie_flag directive in NGINX. This directive automatically adds security attributes to every Set-Cookie header that passes through your server. It includes full SameSite=None support for cross-site cookies.

Adding cookie security flags in NGINX offers several advantages over modifying each application individually:

The cookie flag module operates as an output header filter. It inserts itself into the NGINX header filter chain. Then it inspects every outgoing Set-Cookie header before it reaches the client.

Here is what happens during each request:

  1. The upstream backend (or NGINX itself) generates a response with one or more Set-Cookie headers.
  2. The cookie flag filter examines each Set-Cookie header against your configured rules.
  3. For each matching cookie, the module appends the specified flags (HttpOnly, Secure, SameSite) only if they are not already present.
  4. The modified headers are sent to the client.

The module operates as a header filter rather than a proxy-specific directive. Therefore, it works with all cookie sources: reverse proxy backends, FastCGI (PHP-FPM), uwsgi, SCGI, and cookies set by NGINX modules. This is a key advantage over proxy_cookie_flags, which only modifies cookies from proxy_pass backends.

The module is also idempotent. If a backend already sets HttpOnly on a cookie, the module will not duplicate the flag. You can safely apply it to any backend without worrying about double flags.

Installation

The NGINX cookie flag module is available as a prebuilt dynamic module from the GetPageSpeed repository.

RHEL, CentOS, AlmaLinux, Rocky Linux 8+

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

Then add the following line at the top of /etc/nginx/nginx.conf, before any http, events, or other blocks:

load_module modules/ngx_http_cookie_flag_filter_module.so;

RHEL 7, Amazon Linux 2

sudo yum install https://extras.getpagespeed.com/release-latest.rpm
sudo yum install nginx-module-cookie-flag

Then add the load_module directive to /etc/nginx/nginx.conf as shown above.

Debian, Ubuntu

sudo apt-get update
sudo apt-get install nginx-module-cookie-flag

On Debian and Ubuntu, the module is automatically enabled via a drop-in file. You do not need to add the load_module directive manually.

After installation on any platform, verify that NGINX accepts the configuration:

sudo nginx -t

Configuration Reference

The module provides a single directive: set_cookie_flag.

Syntax

set_cookie_flag <cookie_name|*> [HttpOnly] [secure] [SameSite|SameSite=Lax|SameSite=Strict|SameSite=None];

Context

server, location

Note: The directive is not available in the http block. You must place it inside a server or location block.

Parameters

The first parameter is the cookie name or * (wildcard). Subsequent parameters are the flags to apply. You must specify at least one flag and can specify up to three.

Supported flags:

Flag Effect
HttpOnly Prevents JavaScript access to the cookie via document.cookie
secure Restricts the cookie to HTTPS connections only
SameSite Sets bare SameSite attribute (equivalent to Strict in most browsers)
SameSite=Lax Allows the cookie in top-level navigations but blocks it in cross-site subrequests
SameSite=Strict Blocks the cookie in all cross-site contexts
SameSite=None Cookie sent on all cross-site requests (requires secure flag)

Flag names are case-insensitive. Writing httponly, HttpOnly, or HTTPONLY all produce the same result.

Configuration Examples

Secure All Cookies with a Wildcard

The simplest and most common configuration applies all three security flags to every cookie:

server {
    listen 443 ssl;
    server_name example.com;

    location / {
        proxy_pass http://backend;
        set_cookie_flag * HttpOnly secure SameSite=Lax;
    }
}

This adds HttpOnly, Secure, and SameSite=Lax to every Set-Cookie header from the / location. Any cookie that already has one of these flags will not receive a duplicate.

You can target specific cookies by name and apply different flags to each. Use the wildcard * as a fallback for cookies without explicit rules:

location / {
    proxy_pass http://backend;
    set_cookie_flag PHPSESSID HttpOnly secure SameSite=Strict;
    set_cookie_flag JSESSIONID HttpOnly secure SameSite=Strict;
    set_cookie_flag csrf_token HttpOnly secure SameSite=Lax;
    set_cookie_flag * HttpOnly;
}

Here, PHPSESSID and JSESSIONID get the full set of security flags with SameSite=Strict. The csrf_token uses SameSite=Lax because CSRF tokens must survive top-level form submissions. All other cookies receive only HttpOnly.

Important: When a named rule matches a cookie, the wildcard rule is skipped entirely for that cookie. The wildcard is not additive. If you want PHPSESSID to have all three flags, you must list them all in the named rule.

SameSite=None for Cross-Site Cookies

Since Chrome 80, cross-site cookies require SameSite=None and the secure flag. This applies to embedded iframes, third-party widgets, OAuth flows, and advertising pixels. The NGINX cookie flag module fully supports this requirement:

location / {
    proxy_pass http://backend;
    set_cookie_flag TrackingID SameSite=None secure;
    set_cookie_flag OAuthState SameSite=None secure;
    set_cookie_flag * HttpOnly secure SameSite=Lax;
}

This marks TrackingID and OAuthState as cross-site cookies. Everything else gets a stricter SameSite=Lax default. Without SameSite=None, browsers will block these cookies in cross-site requests. This breaks embedded content and third-party integrations.

Server-Level Defaults with Location Overrides

You can define a default policy at the server level and override it for specific locations:

server {
    listen 443 ssl;
    server_name example.com;

    set_cookie_flag * HttpOnly secure SameSite=Lax;

    location / {
        proxy_pass http://backend;
    }

    location /api {
        proxy_pass http://api-backend;
        set_cookie_flag * HttpOnly secure SameSite=Strict;
    }

    location /embed {
        proxy_pass http://widget-backend;
        set_cookie_flag WidgetSession SameSite=None secure;
        set_cookie_flag * HttpOnly secure SameSite=Lax;
    }
}

The /api location uses SameSite=Strict because API endpoints do not need cross-site cookie access. The /embed location marks WidgetSession with SameSite=None for cross-site iframe embedding. The rest of the site uses SameSite=Lax.

Configuration inheritance works on an all-or-nothing basis. If a child location defines any set_cookie_flag directive, it completely replaces the parent configuration. There is no merging between parent and child.

Production-Grade Quality

The NGINX cookie flag module from GetPageSpeed is built for production environments. It focuses on reliability and correctness.

Full SameSite=None Support

Since Chrome 80 (February 2020), browsers require SameSite=None for cookies sent in cross-site contexts. This module fully supports SameSite=None. It is compatible with modern browser requirements for OAuth flows, embedded iframes, advertising pixels, and third-party integrations.

Memory-Safe Implementation

The module uses NGINX-native memory APIs throughout. All string operations use safe functions like ngx_strlchr and ngx_snprintf. These replace C standard library functions that assume null-terminated strings. NGINX strings (ngx_str_t) are length-delimited, not null-terminated. Using standard C functions like strchr or strcpy on them can read past buffer boundaries. This module handles NGINX strings correctly.

Duplicate Flag Detection

The module validates your configuration at startup. If you specify conflicting SameSite values for the same cookie, NGINX will refuse to start. It reports a clear error:

nginx: [emerg] Duplicate flag "SameSite" (SameSite=Strict) detected

This catches configuration mistakes before they reach production. It prevents silently applying unpredictable behavior.

Idempotent Flag Application

The module checks each Set-Cookie header for existing flags before appending new ones. If a backend already sets HttpOnly, the module will not add a duplicate. This prevents malformed headers and ensures clean Set-Cookie values.

Comprehensive Test Suite

The module includes a full test suite covering every flag combination, wildcard matching, idempotent behavior, and error handling. Automated CI/CD testing runs against both NGINX stable and mainline branches. This ensures compatibility with every release.

NGINX 1.19.3 introduced the built-in proxy_cookie_flags directive. Both approaches set cookie security flags, but they differ in important ways:

Feature set_cookie_flag (this module) proxy_cookie_flags (built-in)
Cookie sources All (proxy, FastCGI, uwsgi, SCGI, NGINX modules) Proxy only (proxy_pass)
Cookie matching Exact name or * wildcard Exact name or ~ regex
SameSite=None support Yes Yes
Variable support No Yes (since NGINX 1.19.8)
Available contexts server, location http, server, location
Requires module install Yes No (built-in since 1.19.3)
Flag removal No Yes (nosecure, nohttponly, nosamesite)
Duplicate detection Yes (rejects conflicting SameSite at config time) No
Idempotent Yes (never duplicates existing flags) No (may duplicate flags)

When to use each:

For a reverse proxy setup, either approach works. The NGINX cookie flag module is particularly valuable for PHP apps served via FastCGI, where proxy_cookie_flags has no effect.

Testing and Verification

After adding the set_cookie_flag directives, verify the configuration syntax:

sudo nginx -t

Then reload NGINX:

sudo systemctl reload nginx

To confirm that cookie flags are applied, send a request and inspect the response headers:

curl -sI https://example.com/ | grep -i set-cookie

You should see output like:

Set-Cookie: PHPSESSID=abc123; path=/; HttpOnly; secure; SameSite=Strict
Set-Cookie: preferences=dark; path=/; HttpOnly

Verifying Idempotency

If your backend already sets some flags, the module will not duplicate them. For example, if your application sends:

Set-Cookie: token=xyz; Path=/; HttpOnly

And your NGINX configuration has set_cookie_flag * HttpOnly secure SameSite=Lax, the result will be:

Set-Cookie: token=xyz; Path=/; HttpOnly; secure; SameSite=Lax

The HttpOnly flag appears only once. The module checks for its presence before appending.

Verifying SameSite=None

For cross-site cookies, confirm that both SameSite=None and secure appear:

curl -sI https://example.com/embed | grep -i set-cookie

Expected output:

Set-Cookie: WidgetSession=abc; Path=/; secure; SameSite=None

Without secure alongside SameSite=None, browsers will reject the cookie entirely.

You can also use browser developer tools. Open the Network tab and check the Response Headers for Set-Cookie entries. The Application tab in Chrome DevTools shows cookie attributes in a structured table.

Troubleshooting

This error means you placed the directive in the http block. Move it to a server or location block:

# Wrong - http block not supported
http {
    set_cookie_flag * HttpOnly;  # ERROR
}

# Correct - server block
server {
    set_cookie_flag * HttpOnly;
}

This error occurs when you define set_cookie_flag twice for the same cookie name:

# Wrong - duplicate cookie name
set_cookie_flag PHPSESSID HttpOnly secure;
set_cookie_flag PHPSESSID SameSite=Lax;  # ERROR

Combine all flags into a single directive instead:

# Correct - all flags in one directive
set_cookie_flag PHPSESSID HttpOnly secure SameSite=Lax;

“Duplicate flag SameSite detected”

This error occurs when you specify two different SameSite values for the same cookie:

# Wrong - conflicting SameSite values
set_cookie_flag Secret SameSite=Lax SameSite=Strict;  # ERROR

Choose one SameSite value per cookie. You cannot apply both Lax and Strict to the same cookie.

“The parameter value is incorrect”

The module accepts these flag values: HttpOnly, secure, SameSite, SameSite=Lax, SameSite=Strict, and SameSite=None. Any other value triggers this error.

“The number of arguments is incorrect”

You must specify between 1 and 3 flags per directive. Zero flags or more than three flags triggers this error:

# Wrong - no flags specified
set_cookie_flag PHPSESSID;  # ERROR

# Correct - at least one flag
set_cookie_flag PHPSESSID HttpOnly;

Flags Not Appearing in Response

If cookie flags are not being added, check these common causes:

  1. Module not loaded: Verify that load_module modules/ngx_http_cookie_flag_filter_module.so; appears at the top of nginx.conf.
  2. Wrong context: Ensure set_cookie_flag is inside the correct server or location block.
  3. Location override: A child location with its own set_cookie_flag replaces the parent rules entirely.
  4. No Set-Cookie headers: The module only modifies existing Set-Cookie headers. It does not create new cookies.

Security Best Practices

Apply All Three Flags to Session Cookies

Session cookies should always have HttpOnly, Secure, and a SameSite value. OWASP recommends this as a minimum:

set_cookie_flag PHPSESSID HttpOnly secure SameSite=Strict;

Use SameSite=Strict for Authentication Cookies

For cookies that manage authentication sessions — session IDs, JWT tokens, or TOTP authentication persistence cookies — use SameSite=Strict. This prevents them from being sent in any cross-site context.

Use SameSite=Lax for General Cookies

For cookies that need to survive top-level navigations (for example, a user clicking a link from an email), use SameSite=Lax. This balances security with usability.

Use SameSite=None Only When Required

Reserve SameSite=None for cookies that genuinely need cross-site access. Examples include third-party widgets, OAuth state tokens, and advertising tracking. Always pair it with the secure flag — browsers reject SameSite=None cookies without secure.

Always Use HTTPS

The Secure flag only works when your site uses HTTPS. Combine it with proper security headers including HSTS to ensure all traffic is encrypted.

Combine with Defense in Depth

Cookie flags are one layer of defense. Additionally, consider:

Performance Considerations

The NGINX cookie flag module has minimal performance impact. It runs only during the header filter phase, once per response. The module:

For responses without Set-Cookie headers, the overhead is a single null check. This is negligible.

Conclusion

The NGINX cookie flag module provides a reliable way to enforce HttpOnly, Secure, and SameSite attributes on cookies. It supports all SameSite values including SameSite=None for cross-site cookies. It works with all backend types, not just proxy_pass. And it never duplicates flags that are already present.

Install it from the GetPageSpeed repository, add a single set_cookie_flag directive, and your cookies gain protection against XSS theft, MITM interception, and CSRF attacks.

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

Exit mobile version