# Secure

> Learn how the Secure cookie attribute ensures cookies are only sent over HTTPS connections. Protect sensitive data from man-in-the-middle attacks.

Source: https://howhttpworks.com/cookies/secure
Last reviewed: 2026-10-04

> **TL;DR:** `Secure` tells 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

```http
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 `Secure` cookie from an insecure origin ("leave secure cookies alone" in the RFC 6265bis storage model), and refuse to let an HTTP response overwrite an existing `Secure` cookie. 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](https://howhttpworks.com/cookies/http-only)), or restrict which sites can trigger it (use [SameSite](https://howhttpworks.com/cookies/same-site)).
- Port and path are not part of the check: `Secure` is 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.

```javascript
// 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".

```nginx
location / {
    proxy_pass http://app;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $host;
}
```

```python
# Django settings.py
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
```

```python
# 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
// 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:

```http
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](https://howhttpworks.com/cookies/domain) and [Path](https://howhttpworks.com/cookies/path).

## Pair it with HSTS

A first visit to `http://example.com` is unprotected until the redirect to HTTPS. [Strict-Transport-Security](https://howhttpworks.com/headers/strict-transport-security) makes the browser upgrade to HTTPS before sending any cookie, closing that gap.

## Verify

```bash
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

- [HttpOnly](https://howhttpworks.com/cookies/http-only)
- [SameSite](https://howhttpworks.com/cookies/same-site)
- [Set-Cookie header](https://howhttpworks.com/headers/set-cookie)
- [Cookie security guide](https://howhttpworks.com/guides/cookie-security)
