HTTP

Header

X-Forwarded-Proto

Learn how the X-Forwarded-Proto header identifies the original protocol (HTTP/HTTPS) used by clients connecting through proxies or load balancers.

2 min read intermediate Try in Playground

TL;DR: Indicates the original protocol (HTTP/HTTPS) used by the client before reaching your server. Essential when SSL terminates at a load balancer.

What is X-Forwarded-Proto?

The X-Forwarded-Proto header identifies whether a client connected via HTTP or HTTPS, which is useful when your app is behind a reverse proxy or load balancer.

Syntax

X-Forwarded-Proto: <protocol>
```text

Common values:

- `http`
- `https`

## Example

```http
X-Forwarded-Proto: https

Why It Matters

Backend apps behind TLS termination often receive plain HTTP from the edge proxy. Without this header, your app may:

  • generate insecure absolute URLs
  • fail to set Secure cookies correctly
  • loop on HTTPS redirect logic

Implementation

app.set('trust proxy', 1) // trust your edge proxy

app.use((req, res, next) => {
  if (!req.secure) {
    return res.redirect(301, `https://${req.headers.host}${req.originalUrl}`)
  }
  next()
})

Security Notes

Like other X-Forwarded headers, this can be spoofed. Only honor it from trusted proxy layers you control.

Standard Alternative

Forwarded can carry equivalent protocol info as proto=https.

HTTPS Detection and Redirect Loops

The most common use of X-Forwarded-Proto is enforcing HTTPS redirects in applications running behind a TLS-terminating load balancer. When SSL terminates at the edge, your application server receives plain HTTP from the load balancer, so req.secure or request.is_secure() returns false even though the original client connected over HTTPS.

Without checking X-Forwarded-Proto, a naive HTTPS redirect middleware would create an infinite redirect loop: the app sees HTTP, redirects to HTTPS, the load balancer forwards it as HTTP again, and the cycle repeats. The fix is to check the forwarded protocol before redirecting.

In Express.js, setting app.set('trust proxy', 1) makes req.secure automatically account for X-Forwarded-Proto, so you can use req.secure directly without reading the header manually. In Django, SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') achieves the same result. In Rails, config.force_ssl = true combined with config.action_dispatch.trusted_proxies handles it.

The security concern with this header is spoofing. A client can send X-Forwarded-Proto: https in a direct request to your origin server, bypassing your HTTPS enforcement. The defense is to only trust this header from known proxy IP ranges and to configure your load balancer to always overwrite (not append) the header value. Never trust a forwarded protocol header that arrives on a port that is not your internal proxy port.

The standardized alternative is the Forwarded header with proto=https, but X-Forwarded-Proto remains the de facto standard across AWS ALB, GCP Load Balancer, Nginx, and Cloudflare.

Frequently Asked Questions

What is X-Forwarded-Proto?

X-Forwarded-Proto indicates the original protocol (http or https) used by the client. Important when SSL terminates at a load balancer and backend sees only HTTP.

Why is X-Forwarded-Proto important?

Without it, your app cannot tell if the original request was HTTPS. This affects secure cookie settings, redirect URLs, and HSTS enforcement.

How do I use X-Forwarded-Proto?

Check X-Forwarded-Proto to determine if request was originally HTTPS. Use this for generating absolute URLs and setting Secure cookie flag appropriately.

Can X-Forwarded-Proto be spoofed?

Yes, like other X-Forwarded headers. Only trust it from known proxies. Configure your load balancer to set it and your app to only accept it from trusted sources.

Keep Learning