The board answers on HTTPS and the console fills with Mixed Content: The page was loaded over HTTPS, but requested an insecure script. Styling is half missing, voting quietly fails, and the links in your sign-in emails point at http://. Your proxy terminates TLS and never passes that fact along, so the app builds every absolute URL on the wrong scheme. One header fixes most of it.
Header behaviour below was checked against each proxy's own documentation on 23 August 2026.
Why does the app think it is running on plain HTTP?
Because from where it stands, it is. TLS ends at the proxy. The hop from the proxy to the container is ordinary HTTP over a private network, and that is deliberate, not a mistake: encrypting a loopback hop buys very little and costs a certificate you then have to renew. The app receives a plain HTTP request, and when it needs to write an absolute URL, into a redirect, a stylesheet tag, a canonical link or an email, it uses what it can see.
The convention that closes the gap is the X-Forwarded-Proto header, which the proxy sets to the scheme the visitor actually used. Whether you have to write it yourself depends entirely on which proxy you run, and this is the detail that catches people moving between them. Caddy's reverse_proxy documentation lists exactly three exceptions to its pass-through default: it sets or augments X-Forwarded-For, and it sets X-Forwarded-Proto and X-Forwarded-Host. nginx has no such default. Its proxy_set_header directive sends what you tell it to send and nothing more, so a configuration copied from a plain HTTP tutorial forwards nothing.
This is not an exotic case. On Fider's repository, the longest proxy thread there, at 27 comments, is somebody whose editing and deleting broke over HTTPS through Apache and Certbot while working perfectly over plain HTTP by IP address. A later report had activation emails carrying the right https:// link, which then produced a 307 redirect to http:// on click. The reporter found the answer themselves: the X-Forwarded-Proto header was missing from their proxy, adding it fixed the redirect, and clearing cookies finished the job.
Why does the page half-load rather than simply fail?
Because browsers treat different resource types differently, and that asymmetry is what makes the symptom so confusing. MDN puts the rule plainly:
auto-upgrading image, video, and audio mixed content requests from HTTP to HTTPS, and block insecure requests for all other resource types
MDN Web Docs, Mixed content
So your logo still appears, because the browser silently rewrote its URL, while the stylesheet and the JavaScript are blocked outright. You get an unstyled page with your images intact and a vote button that does nothing, which reads like a broken application rather than a missing header.
How do you fix it?
1. Tell the app its own public address. Most self-hosted boards have a single setting for this, and Fider calls it BASE_URL. Set it to the full https:// URL, restart the app, and it stops inferring what it can be told.
2. On nginx, send the headers explicitly. All four, not just the scheme, since the others solve the neighbouring problems:
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
3. On Apache, the same idea in a different syntax, inside the TLS virtual host, using mod_headers:
RequestHeader set X-Forwarded-Proto "https"
ProxyPreserveHost On
4. On Caddy or Traefik, change nothing. Both set the forwarded headers already. If you are on one of them and still seeing http URLs, the app's own base URL setting is the suspect, or something is sitting in front of your proxy.
5. Reload the proxy, restart the app, then clear cookies in the browser you are testing with. Session cookies issued during the broken period can be pinned to the wrong scheme and will keep reproducing a fixed bug.
nginx -t && nginx -s reload
docker compose restart app
How do you tell it worked?
Look at the delivered HTML rather than the rendered page, because a browser that auto-upgraded an image will happily hide the evidence:
curl -sS https://board.example.com/ | grep -o 'http://[^"]*' | head
Check that a redirect keeps the scheme, which is the thing that was breaking sign-in:
curl -sSI https://board.example.com/login | grep -i '^location'
Then open the board with the browser console visible, reload with the cache disabled, and confirm the console is empty. Finally, trigger a real sign-in email to yourself and click the link from the message, since email is generated from the app's own settings rather than from a live request and is often the last thing to come right.
One payment, no subscription, unlimited products.
Same symptom, different cause
| What you see | What it usually is |
|---|---|
Asset URLs like http://https://board.example.com/... | The base URL setting already contains the scheme and something is prepending another. Set the value exactly as the app documents it, with no trailing slash |
| Fixed for pages, still wrong in emails | Emails are rendered from the app's stored base URL rather than from request headers, so the setting matters more than the proxy here. Queued messages also keep the old links |
| A redirect loop at sign-in rather than mixed content | The app sets a cookie marked Secure, the request looks insecure, and the browser refuses to return it. Same root cause, different presentation |
| Correct on the first proxy, wrong behind two | The inner proxy is overwriting the outer one's header with its own scheme. Pass the received value through when the hop in front of you is trusted, rather than setting it unconditionally |
| Only one stylesheet or script is insecure | A hard-coded absolute URL in a custom theme, an uploaded image, or an embed. The proxy is fine; the content has the address written into it |
| Correct in Chrome, broken in Safari or an in-app browser | Cached HTML or a service worker holding the old page. Test in a private window before you change anything else |
One caution while you are in this file. If the app container is reachable from anywhere except the proxy, anyone who can reach it directly can send X-Forwarded-Proto: https themselves and the app will believe them. Keep the app on an internal network with no published port, and where your proxy supports a trusted proxy list, use it. Caddy documents trusted_proxies for exactly this, and by default it ignores incoming values of these headers to prevent spoofing. The wider set of headers worth having on a public board is in security headers for a public feedback page.
Does a static support centre hit this?
Less of it, and it is worth being precise about why rather than claiming immunity. Docket has no application server deciding at request time what scheme to write into a page, and no external script or font host that could be requested on the wrong one, because the fonts are self-hosted and there are no third-party scripts. What it does not do is make you immune to your own proxy: put a misconfigured nginx in front of any site and you can still generate redirects on the wrong scheme. The difference is how many moving parts are guessing.
Frequently asked questions
What does X-Forwarded-Proto actually do?
It is a request header the proxy adds, carrying the scheme the visitor used, https or http. The application reads it and uses that value instead of the scheme of the connection it received, which is how it learns that TLS was terminated in front of it. It is a convention rather than a standard, which is why support for it differs between applications.
Do I need HTTPS between the proxy and the container?
Usually not. If both run on the same host or the same private Docker network, the hop never leaves the machine and encrypting it adds a certificate to maintain for very little gain. If the proxy and the app are on separate machines across a network you do not control, that changes, and the hop should be encrypted.
Why did adding the header not fix it on its own?
Two common reasons. The application also stores its own public base URL and uses that for emails and some links, so the setting has to match. And browsers hold on to cookies and cached HTML from the broken period, so a stale session can reproduce a bug that is already fixed. Clear cookies and retest in a private window.
Is mixed content actually dangerous, or just untidy?
It is a real weakness rather than a cosmetic one. An insecurely loaded script can be modified in transit and can then change anything on the page, which is why browsers block scripts and stylesheets outright while merely upgrading images. Treating it as a warning to silence rather than a problem to fix gets the priority backwards.
Does Cloudflare in front of my proxy change any of this?
Yes, and it can create a second version of the same problem. If Cloudflare connects to your origin over plain HTTP, the scheme your proxy sees is genuinely http, and every header in the chain is honestly reporting it. That configuration causes its own well-known failure, covered in ERR_TOO_MANY_REDIRECTS after you turned on Cloudflare.