Self-signed certificate behind a reverse proxy: the redirect loopSolved

QuestionsDawson3 replies

3

Jump to the accepted answer

DawsonMember#1

I am putting a self-signed certificate in front of an application I run on a box, and the browser and the health check both refuse to connect.

The symptom is a redirect loop. The browser says "too many redirects" and curl says the same, and the certificate error is separate: the check fails the TLS validation first, and then, when I tell it to ignore the certificate, it loops.

What I have:

openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj "/CN=app.internal"

and a proxy in front of the app that redirects everything to https. The app itself listens on http://127.0.0.1:3000.

How do I get the certificate trusted and the loop stopped?

3 replies

HermanMember#2

Accepted answer

Two problems, and they are independent. Fix them in this order.

1. The certificate is not trusted. A self-signed certificate is its own issuer, and nobody trusts it. Install it as a root of trust on the machine that connects — on Linux that is /usr/local/share/ca-certificates/ plus update-ca-certificates, and on the machine running the check, not only the server. Until then every client fails validation, which is why curl and the browser both refuse.

2. The loop is the proxy disagreeing with itself. This is the classic one. The proxy decides "the request was not https, redirect to https" based on the header the client sent, and the client is talking to the proxy over http, so the header says http, so the proxy redirects, forever.

The fix is to tell the proxy what the original scheme was, and there are two ways:

location / {
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_pass http://127.0.0.1:3000;
}

or, when the proxy is behind another proxy, to use the header the outer proxy set rather than $scheme. The rule is: the proxy must decide using the scheme the client used, not the scheme of the hop in front of it.

Which one to check first. curl -v https://app.internal/ and read the Location: header on the 301. If the Location says http://, the proxy is generating a redirect to the wrong scheme. If it says https:// and the loop persists, the redirect is coming from the application, not the proxy — the app is generating it because it does not know it is behind a proxy, and it needs the same X-Forwarded-Proto header to decide.

For a certificate that is only valid inside the network, a private certificate authority with one root and a leaf per service is the version of this that scales: install the root once on the clients, and the leafs are per service.

IvanMember#3

In reply to Herman (#2)

The curl -v step is what I would do first too, and the "read the Location header" tip is the part I would not have thought of.

Half the time the redirect in these cases comes from the application, not the proxy — an app that sees a plain request assumes it is not behind TLS and sends you to https itself, and the proxy then does the same. The two together make a loop that looks like a proxy problem.

DawsonAuthorMember#4

In reply to Ivan (#3)

It was both, and in the order you said.

The certificate was the first problem — the check runs on a different host, so the root had to be installed there, not on the server. The loop was the application: it redirected to https because it did not know it was behind a proxy, and adding X-Forwarded-Proto to what the proxy sends fixed it. The proxy itself was innocent.