Cookie attribute
< Set-Cookie: id=a3fWa; SecureSecure
Learn how the Secure cookie attribute ensures cookies are only sent over HTTPS connections. Protect sensitive data from man-in-the-middle attacks.
- Default if omitted
- Absent: also sent over plain HTTP
- Accepts
- Flag only, no value
- Sent back to server
- No: never echoed in the Cookie header
On this page
TL;DR:
Securetells the browser to send the cookie only over HTTPS and to refuse it from insecure responses. Set it on every cookie of an HTTPS site; if cookies vanish behind a load balancer, the app does not know the original request was HTTPS.
What Secure does and does not do
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Lax; Path=/
- The cookie is attached only to requests over HTTPS. A plain
http://request to the same host will not carry it. - Modern browsers (Chrome, Firefox, Safari) also refuse to accept a
Securecookie from an insecure origin (“leave secure cookies alone” in the RFC 6265bis storage model), and refuse to let an HTTP response overwrite an existingSecurecookie. This blocks the classic attack where a network attacker injects a cookie via a spoofed HTTP response. - It does not encrypt the value in the browser, hide it from JavaScript (use HttpOnly), or restrict which sites can trigger it (use SameSite).
- Port and path are not part of the check:
Secureis about scheme only, so the cookie is also sent to other HTTPS ports on the same host.
Localhost
Chrome, Edge and Firefox treat http://localhost as a secure context, so Secure cookies work in development without a certificate. Safari has historically not accepted Secure cookies from http://localhost, so a login that works in Chrome and silently fails in Safari is a sign to use a local HTTPS certificate (mkcert) or to make the flag conditional on environment. http://127.0.0.1 and custom hosts like app.local are treated differently per browser; do not assume they behave like localhost.
Why Secure cookies disappear behind a proxy
TLS usually terminates at nginx, an AWS ALB, or Cloudflare, and the app sees plain HTTP. Frameworks that set Secure only on HTTPS requests then silently drop it, or refuse to set the cookie at all.
// Express: trust the first proxy so req.secure is true when X-Forwarded-Proto: https
app.set('trust proxy', 1)
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { secure: true, httpOnly: true, sameSite: 'lax' }
}))
Without trust proxy, express-session with cookie.secure: true over an HTTP hop does not send Set-Cookie at all. That is the usual cause of “session works locally, new session on every request in production”.
location / {
proxy_pass http://app;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
}
# Django settings.py
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# Flask behind a proxy
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
app.config.update(SESSION_COOKIE_SECURE=True)
// Go net/http
http.SetCookie(w, &http.Cookie{
Name: "session", Value: token, Path: "/",
Secure: true, HttpOnly: true, SameSite: http.SameSiteLaxMode,
})
Cloudflare “Flexible” SSL is a related trap: the browser talks HTTPS to Cloudflare but Cloudflare talks HTTP to your origin. Use Full (strict) and forward X-Forwarded-Proto.
Prefixes: __Secure- and __Host-
Cookie name prefixes make the browser enforce attributes at set time:
Set-Cookie: __Secure-id=a3fWa; Secure; Path=/
Set-Cookie: __Host-session=abc123; Secure; HttpOnly; Path=/; SameSite=Lax
__Secure- requires Secure and an HTTPS origin. __Host- additionally requires Path=/ and no Domain attribute, which pins the cookie to exactly one host and stops sibling subdomains from overwriting it. Prefer __Host- for session cookies. See Domain and Path.
Pair it with HSTS
A first visit to http://example.com is unprotected until the redirect to HTTPS. Strict-Transport-Security makes the browser upgrade to HTTPS before sending any cookie, closing that gap.
Verify
curl -sI https://example.com/login | grep -i '^set-cookie'
# set-cookie: session=...; Path=/; Secure; HttpOnly; SameSite=Lax
In Chrome DevTools, Application, Cookies shows the Secure column. In the Network panel, a blocked cookie shows a warning triangle with the reason (for example “This Set-Cookie was blocked because it had the Secure attribute but was not received over a secure connection”).
Related
Frequently asked questions
What is the Secure cookie attribute?
Secure ensures cookies are only sent over HTTPS connections. It prevents cookies from being transmitted over unencrypted HTTP, protecting against interception.
Should all cookies be Secure?
Yes on any site served over HTTPS. Secure stops the cookie from being sent over plain HTTP, and in modern browsers also stops an insecure origin from overwriting it. It does not encrypt the value or hide it from JavaScript; that is HttpOnly.
Can I set Secure cookies on localhost?
Chrome, Edge and Firefox treat http://localhost as a secure context and accept and send Secure cookies there. Safari has historically refused Secure cookies over plain http://localhost, so test with a local HTTPS certificate (for example mkcert) if Safari matters.
What happens if I access HTTP with Secure cookies?
Secure cookies are not sent over HTTP. The server will not receive them, potentially logging users out or breaking functionality. Always use HTTPS.
Sources
Related
Cookie Prefixes: __Host- and __Secure- Rules
Cookie prefixes make the browser enforce attributes. The exact __Host- and __Secure- rules from RFC 6265bis, how to set them, and why a cookie vanishes.
Partitioned Cookies (CHIPS): The Partitioned Attribute
The Partitioned cookie attribute (CHIPS) gives third-party embeds a separate cookie jar per top-level site. Syntax, Secure requirement, and browser support.
HttpOnly Cookie Attribute: XSS Protection
Learn how the HttpOnly cookie attribute protects against XSS attacks by preventing JavaScript access to sensitive cookies.
SameSite Cookie Attribute: Strict, Lax and None
How SameSite works: Strict, Lax and None, what counts as same-site, why Lax-by-default is Chromium behavior, and how to fix cookies blocked cross-site.