Skip to content

Application vs server

Most hardening advice mixes two different jobs. Separating them is the fastest way to work out why a fix “did nothing”.

The fix is… Whose job
a directive in a daemon’s config file the server
a line in the application you deploy the application

TLS ciphers are the server’s. A cookie’s Secure flag is the application’s. X-Forwarded-For handling inside nginx or HAProxy is the server’s. trust proxy in Express and SECURE_PROXY_SSL_HEADER in Django are the application’s.

This site covers the second column. That is not a claim the first matters less — it is that the two need different people, different access and often different deploys, and conflating them is how a control ends up set in neither place.

Three controls genuinely span both halves, and each produces a bug that looks like the fix failed.

TLS terminates at the proxy; the application receives plain HTTP. Anything the app decides from “was this request secure” now decides wrongly — HTTPS redirects, Secure cookies, CSRF origin checks.

The application must be told to trust a forwarded header, and the proxy must actually set it and strip any client-supplied copy. Configure one side only and you have either a broken app or a spoofable one. That is Django’s SECURE_PROXY_SSL_HEADER and Express’s trust proxy.

Both layers can set them, and when both do you need to know which wins — the answer differs by header and by proxy. Setting the same header in two places is how a site ends up serving two conflicting Content Security Policies, or an X-Frame-Options that contradicts a frame-ancestors directive.

Pick one layer to own them. The application is usually the better choice, because the policy then travels with the code that needs it.

Rate limiting, allow-lists, audit logs and abuse heuristics all depend on it. Behind a proxy the application sees the proxy’s address unless told otherwise — which turns a rate limiter into one shared bucket for every visitor.

Told otherwise too permissively, the value becomes whatever the client claims. Both failure directions are real, and they are the subject of client IP spoofing via trust proxy.

For each of those three, write down which layer owns it before changing either. The common failure is not that someone chose wrong — it is that both sides assumed the other had it.