Nginx – Why are nginx $scheme and $https always “secure” even when request is not

nginx

I'm trying to set up a reverse proxy for an app that handles HTTPS redirect itself (i.e. if it notices that the protocol is not HTTPS it will redirect user to secure site):

server {
    listen 80 default_server;
    listen 443 ssl;
    location / {
        proxy_pass http://localhost:8080;
        proxy_redirect default;
        proxy_redirect https://$proxy_host/ https://$host/;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

The upstream server needs to know whether the original request was over https or not, which it should know via the X-Forwarded-Proto header, assuming $scheme is be "http" on 80 and "https" on 443.

However, it seems that for requests to both 443 and port 80 have $scheme set to "https" and $https set to "on" always — i.e. those variables always say the connection is secure even when it's NOT? Are these variables "hardcoded" based on server configuration, and not the actual incoming request?

Best Answer

My bad. nginx does set $scheme (and presumably $https) as I would expect, per-request.

The trouble was the following node.js code:

var proto = req.headers['x-forwarded-proto'] || (req.connection.encrypted) ? 'https' : 'http';

It looks correct, but the operator precedence is actually wrong! Should be something like:

var proto = req.headers['x-forwarded-proto'] || ((req.connection.encrypted) ? 'https' : 'http');

Without the fix, whenever req.headers['x-forwarded-proto'] was present my local proto would get set to "https" even though the header from nginx was correct!