Private Instance Mode¶
Any Glassy instance can be locked behind sign-in. When Instance Access is set to Private, anonymous visitors never see the landing page, the marketing pages, public author windows, or RSS feeds — they are routed straight to the sign-in screen, and the link they originally clicked is remembered for after they authenticate.
This is the recommended posture for personal appliances, family servers, single-tenant work installs, and any environment where the public web should have zero footprint. A self-hosted appliance is forced into private mode at every boot and cannot be switched out of it.
Private means 'not shared with the world' — not 'invisible to you'
Since v2.36.0-beta.36, a signed-in owner can view their own Public Window on a private instance. Before that, the privacy gate blocked the owner too, which made "private" mean "nobody can see this, including the person it belongs to". Anonymous visitors are unaffected: they still get the sign-in redirect, and the server-side gate is byte-identical. See Public Window.
What changes when Instance Access is Private¶
| Surface | Public mode | Private mode |
|---|---|---|
GET / |
Marketing landing | Redirect to sign-in |
#/, #/about, #/security, #/guide, #/extension, #/neurodiversity, #/obsidian, #/writers, #/students, #/download |
Marketing pages | Redirected to #/login, with the deep link stashed |
#/w/:slug (public author windows) |
Author profile | Redirected to #/login |
GET /api/window/:slug and subpaths |
200 OK |
401 { error: 'members_only', code: 'PRIVATE_INSTANCE' } |
GET /api/window/:slug/feed/rss |
RSS XML | 401 |
GET /w/:slug, GET /w/:slug/:noteId (social-preview HTML) |
Open Graph tags + crawler HTML | 401 { error: 'auth_required', code: 'PRIVATE_INSTANCE' } |
#/login, #/register, #/forgot-password, #/reset-password, #/login-secret |
Reachable | Reachable — sign-in has to keep working |
GET /api/instance |
{ accessMode: 'public', … } |
{ accessMode: 'private', … } |
Two details worth knowing if you are testing this yourself:
- A spoofed crawler User-Agent is not a credential. The social-preview routes are guarded before the User-Agent branch, so announcing yourself as Googlebot does not get you past a private instance.
- Everything behind a session behaves identically in both modes. Private mode changes what an anonymous visitor can reach, not what a signed-in user can do.
Deep links survive the redirect¶
Being redirected to sign-in would be useless if it lost the thing you clicked. It does not.
- You follow a link to
#/w/alice/some-noteon a private instance. - The route is stashed in
sessionStorageand the URL is replaced with#/login. - You sign in — password, 2FA, Google, secret key, or a fresh registration all work the same way.
- You land on the note you asked for, not on a generic dashboard.
The stash is per-tab (sessionStorage, key glassy:postLoginRedirect), so
signing in on one tab does not hijack another, and closing the tab clears it. If
nothing was stashed you land on your notes.
Private browsing may drop the deep link
Some browsers disable sessionStorage in private/incognito windows. Glassy
treats that as non-fatal — sign-in still works, you just land on the default
route instead of the link you clicked. Use a normal window if the deep link
matters.
Registration policy is a separate switch¶
Private mode does not close registration. Whether new sign-ups are accepted is controlled independently by the registration toggle, so all four combinations are legitimate:
| Instance access | Registration | What you get |
|---|---|---|
| Private | Open | Invite-style deployment: anyone with the URL can self-onboard, the public web sees nothing |
| Private | Closed | Pure single-user or family install; an admin creates accounts by hand |
| Public | Closed | Invitation-only community with public marketing pages |
| Public | Open | Fully open instance |
On a self-hosted appliance registration is hard-disabled at the server — not merely toggled off — so the first two rows are the only reachable states there.
Turning it on¶
Self-hosted appliance¶
Private mode is forced at every boot. There is nothing to configure and no way to turn it off; a change attempting to flip it is silently rejected. This is deliberate: an appliance is one person's data on one machine, and a forgotten setting should not be able to publish it.
To seed a new install as private without touching the admin UI, set
INSTANCE_ACCESS_MODE_DEFAULT=private in .env before the first boot.
Cloud, Clear, and other multi-user instances¶
An administrator toggles Admin → Instance Access (formerly labelled Public Windows: Open / Members Only). No redeploy is needed.
The change takes effect within about 60 seconds, because the setting is cached
in-process. Newly opened tabs pick it up immediately — the server injects the
current mode into a freshly served, uncached index.html, and the app reads it
synchronously on first render, so a private redirect happens before any
marketing shell can flash on screen. A tab that is already open holds the
previous mode until it reloads.
Flipping to private breaks RSS readers
Any feed reader polling a now-private #/w/:slug/feed/rss will start
receiving 401s. There is no per-feed escape hatch by design: if you need a
public RSS surface, the instance has to be public. The RSS link is also
hidden from the Public Window UI in private mode, because a plain browser
navigation cannot attach credentials to it.
Migrating an existing deployment¶
No action is required. On the first boot after upgrading:
- The instance-access setting is backfilled from the older Public Windows
field —
Members Onlybecomesprivate, anything else becomespublic— and persisted. - Both fields are kept mirrored on every change, so older admin tooling that reads the legacy field keeps working.
- Reads consult the canonical field first and fall back to the legacy one only if it is absent, so the legacy field can be removed later without another migration.
How this relates to INSTANCE_ID¶
Private mode is one of two independent axes, and it is easy to conflate them:
| Mechanism | What it decides | Who controls it | Changeable at runtime? |
|---|---|---|---|
| Instance access mode | Whether anonymous visitors see anything | Admin toggle (forced private on appliances) | Yes, except on an appliance |
INSTANCE_ID |
What kind of Glassy this is (public, clear, self_hosted) |
Set at deploy time | No — requires a redeploy |
INSTANCE_ID=self_hosted layers permanent server-side gating on top of private
mode: registration is hard-403, the commerce, Stripe and push routes are not
mounted at all, transactional email is disabled, error reporting is not
initialised, and the owner account is auto-promoted to the lifetime Clear tier on
first boot.
For the full matrix of combinations, see Instance Access Modes.
Operational notes¶
GET /api/instanceis cached for 30 seconds viaCache-Control. Expect a worst-case 30-second window before every reverse-proxy layer sees a mode change, even though newly opened tabs are immediate.- The
members_onlyerror string is retained for compatibility. Older clients branch onerror: 'members_only'; newer code should branch oncode: 'PRIVATE_INSTANCE'. Both are present in the same response. GET /api/instanceand the registration-allowance endpoint stay reachable in private mode, because the sign-in screen needs them to render correctly. Neither exposes user data.
Learn more¶
- Instance Access Modes — the
INSTANCE_IDaxis and the full combination matrix - Self-Hosting Glassy — the appliance, which is always private
- Clear community instance — operator runbook for a members-only community
- Public Window — what a window exposes, and to whom