Request header · non-standard
> X-Forwarded-Proto: httpsX-Forwarded-Proto
Learn how the X-Forwarded-Proto header identifies the original protocol (HTTP/HTTPS) used by clients connecting through proxies or load balancers.
- Direction
- Request
- Category
- Proxies
- Spec
- MDN reference
- Status
- Non-standard: De-facto convention; the standard form is Forwarded
- JS can set it
- Yes
- CORS-safelisted
- No: may trigger a preflight
On this page
TL;DR:
X-Forwarded-Proto: httpstells an origin the client-facing scheme reported by a proxy. It can fix HTTPS redirect loops after TLS termination, but only if the proxy overwrites the field and the app trusts that proxy.
Why This Header Exists
A TLS-terminating proxy can forward over plain HTTP:
Browser --HTTPS--> Reverse proxy --HTTP--> App
The backend socket then reports HTTP. This illustrative field carries the outer scheme:
X-Forwarded-Proto: https
MDN documents it as a de-facto field, not an HTTP standard.
Apps use the request scheme for redirect decisions and absolute URL generation. Cookie middleware can also depend on whether a request is considered secure. These behaviors depend on the framework; merely receiving the header does not change them.
The Classic Redirect Loop
If the app redirects every HTTP socket to HTTPS, each browser retry arrives through the same HTTP backend connection and gets redirected again. The fix is to have scheme detection use verified proxy information rather than just the inner socket.
Framework Handling
For Express behind nginx on the same machine, with the app bound to loopback, use:
import express from 'express'
const app = express()
app.set('trust proxy', 'loopback')
app.get('/scheme', (req, res) => {
res.json({ protocol: req.protocol, secure: req.secure })
})
app.listen(3000, '127.0.0.1')
Inside nginx’s existing TLS-terminating proxy location:
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://127.0.0.1:3000;
The proxy overwrites a supplied value. Express then uses it for req.protocol and req.secure. This assumes local processes are trusted and nginx receives the client connection directly. See Express behind proxies and nginx proxy_set_header.
A numeric trust proxy setting counts hops; it is not an allowlist of proxy identities. Different-length routes can make a client closer than the configured count. Prefer a policy that matches the actual network.
After configuring proxy trust, a redirect can use req.secure. Place this before application routes and use your configured public hostname:
app.use((req, res, next) => {
if (!req.secure) {
const path = new URL(req.originalUrl, 'http://internal')
const target = new URL('https://app.example.com')
target.pathname = path.pathname
target.search = path.search
return res.redirect(308, target.href)
}
next()
})
The configured origin keeps the destination authority under application control; only the request path and query are copied. 308 preserves the request method; a redirected POST remains a POST. MDN’s 308 reference describes that behavior. Behind an HTTP-only edge listener, this middleware always redirects; the edge must also provide the HTTPS route.
Security Rule: Trust Boundaries Matter
For the setup above, send a forged header through nginx and inspect /scheme:
curl -i -H 'X-Forwarded-Proto: http' https://app.example.com/scheme
Replace the hostname with your TLS virtual host. The app should report the HTTPS edge scheme, because nginx overwrites the supplied field. If a CDN terminates TLS before nginx, $scheme instead describes CDN-to-nginx transport; use that provider’s documented scheme signal and restrict who can reach the origin.
Do not build redirect destinations from an unchecked Host value. A configured public origin avoids making the request choose a redirect hostname.
Standard Alternative
Forwarded expresses the scheme as proto=https. Switching names requires explicit support in the application; Express’s documented trust proxy handling uses X-Forwarded fields.
Related Headers
Frequently asked questions
What is X-Forwarded-Proto?
X-Forwarded-Proto tells an origin app whether the original client connected to the edge over HTTP or HTTPS before a proxy forwarded the request inward.
Why is X-Forwarded-Proto important?
Without it, an app behind TLS termination may think every request is plain HTTP. That affects redirects, secure cookies, absolute URL generation, and security behavior.
How do I use X-Forwarded-Proto?
Use it only through trusted proxy configuration. Express uses it for req.protocol when trust proxy accepts the proxy address.
Can X-Forwarded-Proto be spoofed?
Yes. Never trust it from arbitrary clients. Only trust it when your proxy overwrites it and your app accepts it only from known proxy layers.
Sources
Related
Access-Control-Request-Headers Header
Learn how Access-Control-Request-Headers tells servers which custom headers will be used in CORS requests. Essential for preflight request handling.
Access-Control-Request-Method Header
Learn how Access-Control-Request-Method tells servers which HTTP method will be used in the actual CORS request. Essential for preflight request handling.
Origin Header
Learn how the Origin header identifies where cross-origin requests come from. Essential for CORS security policies and preventing cross-site request forgery.
Sec-Fetch-Dest Header: All Values Explained
Sec-Fetch-Dest tells the server where a response will be used: document, iframe, image, script, empty. Full value list and server-side uses.