๐ Checking Connection Security
Every user sees, at the bottom of LibreFolio's sidebar, how safely their browser talks to the server: Connection: secure, Connection: local network or Connection: not secure. The user guide, Connection Security, explains the three levels to them. This page is for whoever runs the server: how to check that your deployment gives its users Connection: secure, and how to fix it when it does not.
The goal is an HTTPS address, from Tailscale Serve or Funnel
(Exposing Securely) or from a reverse proxy with a certificate
(Behind a reverse proxy), and a session cookie marked Secure.
โ Checklist
Go through it once your HTTPS address works, and again after any change to the proxy or to .env.
You need an administrator account and, for the last two checks, a phone on mobile data.
- Open the HTTPS address and sign in. The indicator shows ๐ข Connection: secure, and its details say that the connection is encrypted with HTTPS.
- Look for the cookie line. Signed in as an administrator, the details show only that reason and the How to connect securely link. One more line, about the session cookie, means that LibreFolio does not know about the HTTPS: see Behind a reverse proxy.
- Ask the server. In a new tab, open
https://<your address>/api/v1/system/connection: the answer contains"cookie_secure":true(The server's view). - Look at the cookie. In the browser's developer tools, the
sessioncookie is markedSecure(The session cookie). - From outside, open the HTTPS address. On the phone, with Wi-Fi off, the page loads and, once you sign in, the indicator shows ๐ข Connection: secure. Keep Tailscale off on the phone for a Funnel or reverse-proxy address; switch it on for a Tailscale Serve address, which only your tailnet can open.
- From outside, try plain HTTP. On the same phone,
http://<your public IP or name>:6040, or your ownPORT, does not load. If the login page appears, plain HTTP is open to the internet: do not sign in there, since the password would travel in clear text, and close the port (When a check fails). Whoever signs in over that address sees ๐ด Connection: not secure, or ๐ Connection: local network with an Unclear reason when the server cannot see where visitors come from (Limits).
Every check passed? Ask your users to use the HTTPS address on every device and to update their old bookmarks (How to connect securely).
๐ถ No HTTPS address yet?
With Level 1 or 2 of Exposing Securely, users reach LibreFolio over plain
HTTP. Check the level each address gets (What the indicator shows) and that plain
HTTP does not load from outside (check 6). The cookie checks do not apply: over plain HTTP the
session cookie is not Secure, and "cookie_secure":false is expected.
๐ฆ What the Indicator Shows
The browser judges the address it opened; the server then reports where it sees the request coming from, and can confirm that verdict or make it uncertain, but never make it secure. What your users see, by the way they reach LibreFolio:
| How users reach LibreFolio | Example | Level |
|---|---|---|
| Tailscale Serve or Funnel, or a reverse proxy with a certificate | https://librefolio.your-tailnet.ts.net |
๐ข secure: HTTPS |
| On the server itself | http://localhost:6040 |
๐ข secure: localhost |
| Tailscale IP or full MagicDNS name, plain HTTP (Level 1) | http://100.a.b.c:6040 |
๐ข secure: VPN (Tailscale) |
| Short MagicDNS name, plain HTTP: a name without a dot counts as local | http://your-server:6040 |
๐ local network |
| Local address, also through a subnet router (Level 2) | http://192.168.1.100:6040 |
๐ local network |
| A port opened on the router, plain HTTP | http://myhome.duckdns.org:6040 |
๐ด not secure |
When the server cannot see your visitors' addresses, the last row shows ๐ local network with an Unclear reason instead (Limits). The user guide gives the exact rules under each level: address ranges, names and the Unclear cases (Connection Security).
๐ The Server's View
The indicator asks the server how it sees the connection, once per page load:
GET /api/v1/system/connection. You can read the same answer: signed in, open this address in a
new tab of the same browser, with your own LibreFolio address in place of the example's:
The browser shows a one-line answer, for example:
Any account works, not only an administrator. An answer such as {"detail":"Not authenticated"}
means that there is no session at that address: sessions belong to an address, so signing in at
http://192.168.1.100:6040 does not sign you in at the HTTPS address. And since the indicator asks
only once per page load, reload LibreFolio after a change to see its new verdict.
cookie_secure tells whether a session cookie set on this request would be Secure, following
SESSION_COOKIE_SECURE. On the HTTPS address it must be true: false is what
shows the administrator line about the cookie (Behind a reverse proxy). Over plain
HTTP, false is expected.
client_class is where the server sees the request coming from. It reads the last
X-Forwarded-For value, the one the nearest proxy added, when it is an IP address, and otherwise
the address of the connection itself. Only the class leaves the server: the address is never
returned or logged.
client_class |
The request comes from |
|---|---|
loopback |
The server itself: 127.0.0.0/8 or ::1 |
vpn |
Tailscale or Headscale: 100.64.0.0/10 or fd7a:115c:a1e0::/48 |
lan |
A local network: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, fe80::/10 or fc00::/7 |
public |
Any other address: the internet |
unknown |
No address, or one that cannot be read |
The class counts only over plain HTTP, and not on the server itself (localhost):
- with a public address in the browser,
publicorunknownconfirms Connection: not secure, whileloopback,vpnorlanturn it into Connection: local network, as unclear; - with a local or Tailscale address in the browser,
publicmakes the verdict unclear, and any other class leaves it as it is.
On the HTTPS address the class changes nothing, but it tells you whether the server can see your
visitors' addresses: from the phone on mobile data, with Tailscale off, public means it can, and
lan or loopback mean it cannot (Limits).
๐ช The Session Cookie
The cookie that keeps a user signed in is called session. LibreFolio sets it at sign-in, always
HttpOnly, so scripts in the page cannot read it, and SameSite=Lax. Whether it is also Secure,
sent over HTTPS only, depends on SESSION_COOKIE_SECURE in .env:
auto, the default:Securewhen the request is HTTPS, seen directly or because the firstX-Forwarded-Protovalue sent by a reverse proxy ishttps;always: alwaysSecure;never: neverSecure.
Case and spaces around the value do not matter; any other value stops LibreFolio at startup. When
always or never is the right choice: Configuration.
The flag is decided at sign-in: the cookie a browser already holds keeps the flag it got then. After fixing the proxy or the option, sign out and sign in again.
To see the flag in the browser:
- Sign in at the HTTPS address.
- Open the developer tools:
F12, or right-click the page and choose Inspect. Safari shows them only once its developer features are turned on in its settings. - Open the cookies of your LibreFolio address: Application โ Cookies in Chrome and Edge, Storage โ Cookies in Firefox and Safari.
- In the
sessionrow, Secure is ticked (truein Firefox), and so is HttpOnly; SameSite readsLax.
The console cannot show it: document.cookie leaves out HttpOnly cookies.
๐งฐ Behind a Reverse Proxy
A reverse proxy receives the HTTPS connection and passes it to LibreFolio over plain HTTP, so LibreFolio learns about the HTTPS, and about the visitor, only from the proxy's headers:
X-Forwarded-Proto: httpssays that the browser uses HTTPS: withSESSION_COOKIE_SECURE=auto, the default, LibreFolio then marks the session cookieSecure;X-Forwarded-Forcarries the visitor's address, which lets the indicator see where users connect from.
Tailscale Serve and Funnel, Caddy and Traefik send both on their own. Nginx needs both lines in the
location block that passes requests to LibreFolio:
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
If the browser uses HTTPS but the session cookie is not marked Secure, because the proxy does not
send X-Forwarded-Proto or because SESSION_COOKIE_SECURE is never, administrators see one more
line in the indicator's details. The level stays Connection: secure, because the connection is
encrypted; only the cookie lacks Secure:
Your browser uses HTTPS, but the session cookie is not marked Secure. Make the reverse proxy send X-Forwarded-Proto (Nginx:
proxy_set_header X-Forwarded-Proto $scheme;), or set SESSION_COOKIE_SECURE back to auto if it is never.
Once the proxy is fixed, reload LibreFolio: the line disappears. Then sign out and sign in again,
so that the browser gets a Secure cookie.
โ๏ธ Set SESSION_COOKIE_SECURE instead
Sending the header is the better fix. If you only ever reach LibreFolio over HTTPS, you can
instead add this line to .env:
Then restart LibreFolio: with Docker, run docker compose up -d (a plain
docker compose restart keeps the old environment); on a host installation, restart
./dev.py server, which reads .env too. Then sign out and sign in again.
Use never only for the rare proxy that claims HTTPS while the browser is on plain HTTP. See
Configuration for every option.
๐ ๏ธ When a Check Fails
| What you see | Why | What to do |
|---|---|---|
| The HTTPS address does not show Connection: secure | The page is not on HTTPS: the address bar shows http:// |
Open the https:// address, and have the proxy redirect http:// to it |
The line about the session cookie, or "cookie_secure":false on the HTTPS address |
The proxy does not send X-Forwarded-Proto: https, or SESSION_COOKIE_SECURE is never |
Fix the proxy (Behind a reverse proxy) or set the option back to auto, then reload |
"cookie_secure":true, but the session cookie is not Secure |
The cookie was set before the fix | Sign out and sign in again |
{"detail":"Not authenticated"} |
No session at this address | Sign in at this address, in the same browser |
| Every sign-in ends on the login page again | The cookie is Secure, but the browser uses plain HTTP: SESSION_COOKIE_SECURE=always, or a proxy that sends X-Forwarded-Proto: https to a plain-HTTP browser |
Use the HTTPS address, or fix the proxy: see Configuration |
| From outside, the HTTPS address does not load | A Tailscale Serve address is private; a new Funnel needs approval and a few minutes | Switch Tailscale on on the phone, or use Funnel; approve the Funnel and wait (Exposing Securely) |
From outside, plain http://โฆ:6040 loads |
LibreFolio's port is open to the internet: a port forward on the router, or a server with a public address | Close it: remove the port forward, or block the port in the server's firewall. Tailscale needs no open port, and a reverse proxy only its own |
client_class reads lan or loopback from mobile data, or plain HTTP from the internet shows Connection: local network, Unclear |
The server cannot see where visitors come from | See Limits; with Nginx, add the X-Forwarded-For line |
| A Tailscale address shows Connection: local network | A short MagicDNS name has no dot, so it counts as local | Use the Tailscale IP, the full .ts.net name or, better, the HTTPS address |
โ ๏ธ Limits
The indicator is advice, not a check: it blocks nothing. Two limits matter when you check a deployment.
It cannot always see where visitors are. With Docker Desktop exposed directly, or behind a
proxy that does not pass the visitor's address in X-Forwarded-For, the server sees every visitor
as local: lan, or loopback for a proxy on the same machine. Plain HTTP from the internet then
shows Connection: local network, as unclear, instead of Connection: not secure. Docker
Engine on Linux keeps the visitor's address. HTTPS addresses are not affected: they show
Connection: secure either way.
It does not check certificates. A self-signed certificate that a user accepted in the browser shows Connection: secure: the traffic is encrypted, but nobody vouches that the server is really yours.
There is no list of trusted proxies to set up. X-Forwarded-For only feeds the indicator, so a
forged value misleads only the indicator of whoever sent it; X-Forwarded-Proto can only turn
Secure on, so a forged https breaks only the sender's own sign-in.
๐ Related
- ๐ Connection Security โ The user guide: the three levels and what to do at each
- ๐ Exposing Securely โ Tailscale, from a private VPN to a public HTTPS address
- ๐ณ Advanced Docker โ Compose file, environment variables, reverse proxy
- ๐ Configuration โ
SESSION_COOKIE_SECUREand every other.envoption