Skip to main content

NGINX / Security / Server Setup

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

by , , revisited on


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.

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) or intercept them over unencrypted connections. The NGINX cookie flag module solves this problem at the reverse proxy layer, enforcing 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, including full SameSite=None support for cross-site cookies.

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

  • Centralized enforcement. A single NGINX configuration secures cookies from all backends — PHP-FPM, Node.js, Python, Java, and any other upstream. You do not need to touch application code.
  • Legacy application support. Many older frameworks and third-party backends do not set HttpOnly, Secure, or SameSite on their cookies. The NGINX cookie flag module fills that gap without requiring application upgrades.
  • Universal cookie coverage. Unlike the built-in proxy_cookie_flags directive, this module works with all cookie sources: reverse proxy backends, FastCGI (PHP-FPM), uwsgi, SCGI, and even cookies set by NGINX modules like userid.
  • Defense in depth. Even if an application developer forgets to set cookie flags, NGINX catches the omission. This provides an additional security layer that operates independently of application logic.
  • Simple configuration. A single set_cookie_flag directive can secure all cookies with a wildcard, or you can define granular per-cookie policies.

The cookie flag module operates as an output header filter. It inserts itself into the NGINX header filter chain and 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.

Because the module operates as a header filter rather than a proxy-specific directive, 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 the built-in proxy_cookie_flags directive, 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. This means you can safely apply it to any backend without worrying about double flags in your Set-Cookie headers.

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 configuration 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 in responses 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;
}

In this configuration, 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 from external sites. 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 HttpOnly, Secure, and SameSite=Strict, you must list all three flags in the named rule.

SameSite=None for Cross-Site Cookies

Since Chrome 80, cookies that need to be sent in cross-site contexts (such as embedded iframes, third-party widgets, OAuth flows, and advertising pixels) must be marked with SameSite=None and the secure flag. 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 configuration marks TrackingID and OAuthState as cross-site cookies while applying a stricter SameSite=Lax default to everything else. Without SameSite=None, browsers like Chrome, Firefox, and Edge will block these cookies in cross-site requests, breaking 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 typically do not need cross-site cookie access. The /embed location marks the WidgetSession cookie 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 of individual cookie rules between parent and child.

Production-Grade Quality

The NGINX cookie flag module from GetPageSpeed is built for production environments with a focus on reliability and correctness.

Full SameSite=None Support

Since Chrome 80 (released February 2020), browsers require SameSite=None for cookies that must be sent in cross-site contexts. This module fully supports SameSite=None, making it 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 NGINX functions like ngx_strlchr and ngx_snprintf instead of C standard library functions that assume null-terminated strings. NGINX strings (ngx_str_t) are length-delimited, not null-terminated, so 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 accidentally specify conflicting SameSite values for the same cookie, NGINX will refuse to start and report a clear error:

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

This catches configuration mistakes before they reach production, rather than 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 on a cookie, the module will not add a second HttpOnly flag. This prevents malformed headers and ensures clean Set-Cookie values regardless of what the upstream sends.

Comprehensive Test Suite

The module includes a full test suite that verifies every flag combination, wildcard matching, idempotent behavior, per-cookie rules, and error handling. Automated CI/CD testing runs against both NGINX stable and mainline branches to ensure 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:

  • Use set_cookie_flag if you have FastCGI (PHP-FPM), uwsgi, or SCGI backends, or if cookies originate from NGINX modules like userid. Additionally, choose this module when you want idempotent flag application and config-time validation of conflicting SameSite values.
  • Use proxy_cookie_flags if you only use proxy_pass backends, need regex-based cookie matching, need variable support, or need to remove flags from cookies.

For a reverse proxy setup, either approach works. The NGINX cookie flag module is particularly valuable for PHP applications 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 because 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 the secure flag alongside SameSite=None, browsers will reject the cookie entirely.

You can also use browser developer tools. Open the Network tab, select a request, and check the Response Headers section for Set-Cookie entries with the expected flags. The Application tab in Chrome DevTools shows cookie attributes in a structured table, making it easy to verify each flag.

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 within the same block:

# 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 only accepts these exact 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. Providing 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 (not required on Debian/Ubuntu).
  2. Wrong context: Ensure set_cookie_flag is inside the correct server or location block that handles the request.
  3. Location override: If a child location defines its own set_cookie_flag, it completely replaces the parent configuration. Verify that your target location has the expected rules.
  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. This is the minimum recommended by OWASP:

set_cookie_flag PHPSESSID HttpOnly secure SameSite=Strict;

Use SameSite=Strict for Authentication Cookies

For cookies that manage authentication sessions (such as session IDs, JWT tokens stored in cookies, or TOTP authentication persistence cookies), use SameSite=Strict to prevent 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 to your site 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, such as third-party widgets, OAuth state tokens, or advertising tracking. Always pair SameSite=None with the secure flag — browsers reject SameSite=None cookies without secure.

Always Use HTTPS

The Secure flag only makes sense when your site is served over HTTPS. Combine it with proper security headers including HTTP Strict Transport Security (HSTS) to ensure all traffic uses encrypted connections.

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 operates only during the header filter phase, which runs once per response. The module:

  • Iterates only over Set-Cookie headers, not all response headers
  • Performs case-insensitive string comparisons to detect existing flags
  • Allocates small amounts of memory from the request pool (freed automatically after the request completes)

For responses without Set-Cookie headers, the overhead is a single null check on the configuration pointer, which is negligible.

Conclusion

The NGINX cookie flag module provides a reliable way to enforce HttpOnly, Secure, and SameSite attributes on cookies at the reverse proxy layer. It supports all SameSite values including SameSite=None for cross-site cookies, works with all backend types (not just proxy_pass), and 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-based theft, man-in-the-middle 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

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.