Ask
29

CSRF verification fails only after putting the app behind a reverse proxy — origin does not match, or the cookie is not set

The application works perfectly when I run it directly and reach it on a local port. Once it is behind a reverse proxy with TLS and a real hostname, any form submission fails CSRF verification.

The error varies. Sometimes it complains that the origin does not match any trusted origins. Sometimes it says the CSRF cookie is not set. The admin login is the easiest place to reproduce it.

I have added the hostname to the allowed hosts setting, which fixed a different error earlier but not this one.

What is the framework actually checking, and what is the complete set of settings I need for this deployment shape?

3 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @proxy_headers_paul · 4h ago

    Point four is the one people miss, and it is on the proxy rather than the application, so nobody looks there.

    Several proxies default to sending the backend's address as the Host header rather than the one the browser used. The application then sees an internal name, fails the allowed-hosts check or builds the wrong origin, and no amount of adding your public hostname to the application's settings helps because your public hostname never arrives.

    It is one line of proxy configuration in every proxy, but it is a different line in each of them. Check yours specifically rather than trusting that it does the obvious thing.

    Same family of problem: proxies that strip or rewrite the forwarded protocol header, and chains of two proxies where only the outer one sets it correctly.

    27
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @venv_veronica · 7h ago

    For anyone hitting the "cookie not set" variant specifically, one cause that is not about proxies at all: the cookie is only issued when something on the page requests it.

    If you have a page that posts a form but the token was never rendered — a template that omits it, an API-style endpoint being posted to from JavaScript, an aggressively cached page served without the cookie — then there is genuinely no cookie to send, and no header configuration will produce one.

    Worth ruling out by checking whether the cookie exists in the browser at all before assuming the proxy is at fault. If it is absent in the browser, the problem is upstream of everything discussed above.

    Caching is the sneaky one: a cached HTML response can carry a token belonging to a different session.

    21
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @django_dependable · 4h ago

    Three separate checks are involved and they fail with different messages, which is why this feels inconsistent. Once you separate them the fix is mechanical.

    Check one: the Host header. The framework compares the incoming host against the allowed hosts list and rejects anything else. That is the error you already fixed. It is unrelated to CSRF and it is why fixing it did not help.

    Check two: the Origin or Referer header against the trusted origins list. This is your first error message. On an unsafe request the framework compares the browser-supplied origin against a configured list, and here is the part that catches everyone: the entries must include the scheme. A bare hostname does not match. You need the full https://host form, and if you also reach it on another name, that name needs its own entry.

    Check three: the cookie. Your second error message means the browser did not send the CSRF cookie back, and there are three usual reasons:

    • The cookie is marked secure and you are on plain HTTP somewhere. If the setting for a secure CSRF cookie is on, the browser will only send it over HTTPS. Mixed access — HTTPS from outside, plain HTTP when you test internally — produces exactly this, intermittently, depending on how you happened to reach it.

    • The framework thinks the request is not secure. This is the big one for your deployment shape and the root of most of this. Your proxy terminates TLS and forwards plain HTTP to the application, so the application sees an insecure request. It then builds redirects as http://, decides differently about secure cookies, and disagrees with the browser about the origin.

      The fix is the setting that tells the framework to trust a forwarded protocol header, so it treats requests as secure when the proxy says they were. Only set this when a proxy you control is genuinely in front and is overwriting that header, because if a client can set it themselves you have handed them a way to make the framework believe an insecure request was secure.

      Your proxy must also actually send that header. Most do by default; confirm rather than assume.

    • The host in the cookie's domain does not match the host you are browsing. Usually from setting a cookie domain explicitly when you did not need to. If you have set one, try removing it.

    The complete checklist for this deployment shape, which is what you asked for:

    1. Allowed hosts includes every hostname it is reached by.
    2. Trusted origins includes each of those with the scheme.
    3. The proxy protocol header setting is enabled, and the proxy sets that header.
    4. The proxy forwards the original Host header rather than rewriting it to the backend address.
    5. Secure cookie settings are on, and every path to the app is HTTPS.

    Debugging tip that finds it fastest: log the incoming headers at the application for one request. You will see immediately whether the host is what you expect, whether the forwarded protocol header is present, and whether the cookie came back. Nearly every case of this is visible in one dump of the headers, and it beats changing settings one at a time.

    30
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report