Private Docusaurus: What Actually Gates the Site, and What Still Leaks Through sitemap.xml, Search and Source Maps
Docusaurus has no login, no user table and no server-side session, because a production build is a folder of static HTML, JavaScript and JSON that any web server can hand out. Every gate you add lives in front of it: HTTP basic auth, a forward-auth proxy, or an access rule on the static host. That gate is genuinely effective, since an unauthenticated request never reaches a single file. The real risk is not the gate failing, it is the build itself, because sitemap.xml , the prebuilt search index and unstripped source maps each contain a readable copy of what you thought you were hiding, so one misrouted path or one cached response leaks the whole set at once. TL;DR by reader profile: - Family running a shared household wiki (passwords, router notes, insurance scans in a Docusaurus site on a home server): use a forward-auth proxy with real accounts, not one shared basic-auth password, because a single credential cannot be revoked for one person only. - Solo maintainer with a private notes site (one person, one laptop, one VPS): HTTP basic auth over HTTPS is proportionate, provided you disable the sitemap plugin and ship no source maps. - Small team with an internal handbook (8 to 20 people, staff turnover): forward auth against an identity provider, so account removal is one action and not a redeploy of a password file. - Consultancy under a client NDA (contract requires access logs and named users): forward auth plus per-request logging, since basic auth gives you no reliable identity in the access log beyond a shared username. - Open source project with a private staging site (public docs plus an unreleased version branch): keep the private content in a separate build entirely, because noIndex and unlisted pages hide pages from indexes, not from anyone who requests the URL. The central tradeoff: basic auth costs one configuration block and no moving parts but gives you one shared secret and no revocation, while forward auth gives you named accounts, logout and audit trails at the cost of running an identity service that must stay up for anyone to read a single page. Table of contents - What does Docusaurus actually serve, and why is there nothing to log into? - Where can you put the authentication gate in front of a Docusaurus build? - Is HTTP basic auth enough for a private Docusaurus site? - Basic auth against forward auth against static host rules: what each one costs - What ends up in the build folder that you did not expect to publish? - How much does sitemap.xml leak, and should you disable the plugin? - Does the prebuilt local search index expose the full text of your docs? - Are source maps in a Docusaurus production build a real risk? - Should unauthenticated visitors get 401, 403 or 404? - Which cache and CDN headers decide whether a private page gets stored publicly? - How do you keep drafts, unlisted pages and old doc versions out of the build? - What breaks in Docusaurus once it sits behind an auth proxy? - Which gate should each profile choose? - Self-hosting, hosting location and data sovereignty for a private Docusaurus site What does Docusaurus actually serve, and why is there nothing to log into? Run npm run build and Docusaurus 3.x writes a build/ directory. That directory is the entire application. There is no PHP process, no database connection string, no session store and no user table, so there is no code path where a password could be checked. The React you wrote is prerendered to HTML at build time and rehydrated in the browser, which means the server's only job is to return files. What the build directory contains: - Prerendered HTML per route: every doc page exists as a complete index.html on disk, readable withcurl and no JavaScript engine at all. - A client JavaScript bundle: the hashed assets/js/ files that hydrate the page, plus a runtime manifest listing every route the site knows about. - Static assets copied verbatim: anything you dropped in static/ is served at the site root, including PDFs and images you may have forgotten. - Generated metadata files: sitemap.xml from@docusaurus/plugin-sitemap ,robots.txt , RSS and Atom feeds from the blog plugin, and search index JSON if you use a local search plugin. - No server-side configuration: docusaurus.config.js runs during the build, not at request time, so it cannot make an access decision about an incoming request. The practical consequence is blunt. npx docusaurus serve and python3 -m http.server are equally unauthenticated, and so is Nginx, Caddy or an S3 bucket pointed at the same folder. Every access control you get comes from the layer in front of those files, never from Docusaurus itself. Where can you put the authentication gate in front of a Docusaurus build? There are four layers where a request can be stopped, and you should pick exactly one as the authoritative gate. Two overlapping gates usually mean one of them is misconfigured and nobody notices. The web server that serves the files: Nginx auth_basic with an htpasswd file, or a Caddy basic_auth directive, checks credentials before it opens build/index.html . A reverse proxy in front of that server: Traefik, Caddy or Nginx running auth_request against a forward-auth service such as Authelia or oauth2-proxy, which returns 200 or 401 for every path including /sitemap.xml . The static hosting platform: Netlify password protection, Cloudflare Access or an S3 bucket policy, decided outside your repository and outside your build. The network boundary: a WireGuard or Tailscale tunnel, where the site listens on a private address and no gate exists at HTTP level at all. Where that stack physically runs is a separate decision: a rented VPS, a home server or NAS on your own connection, or a managed box. Yundera is a managed Personal Cloud Server, built on CasaOS, that runs self-hosted apps as Docker containers on a server dedicated to the user. On any of those, the gate still belongs to the proxy or web server, because Docusaurus contributes nothing. One warning about the development server. npm start binds port 3000 with hot reload, no gate and no build step, and it happily serves your unfinished private pages to anything that can reach that port. Never expose it, even briefly, and never treat it as a preview environment for people outside the household or team. Is HTTP basic auth enough for a private Docusaurus site? For one person, yes. For a household or a team, it degrades badly, and the failure is social rather than cryptographic. Over HTTPS the credential is protected in transit, so the weaknesses are all about lifecycle. The password is shared, so revocation is all or nothing: removing one person's access means changing the single entry in .htpasswd and telling everyone else the new password, which in practice nobody does. Hashing depends on how you generated the file: run htpasswd -B -c /etc/nginx/.htpasswd alice to force bcrypt, because the default on older htpasswd builds is MD5 based crypt, and Nginx will accept both without warning you. There is no logout: the browser caches the credential for the origin until the tab or the browser is closed, so a family laptop left open stays authenticated for anyone who picks it up. Access logs give you no real identity: every request carries the same username, so a log line proves someone had the password, not who. Automation gets awkward: any script, RSS reader or link checker now needs the credential embedded, and curl -u user:pass https://docs.example.com/sitemap.xml in a shell history file is a common way for it to escape. Prompts confuse non-technical readers: the native browser dialog offers no branding, no password reset and no explanation, which generates support requests from the exact people you set the site up for. Use basic auth when the reader count is 1 to 3 and stable. Above that, the shared secret becomes the weakest part of the system. Basic auth against forward auth against static host rules: what each one costs The three approaches differ less in strength than in what they demand from you every month after setup. Compare them on operational cost, not on theoretical security. | Approach | What it costs to run | Where it breaks | |---|---|---| Web server basic auth (Nginx auth_basic , Caddy basic_auth ) | One config block and one .htpasswd file, no extra process, no extra memory | Shared secret, no logout, no per-person revocation, credential leaks into scripts | Forward auth (Authelia or oauth2-proxy behind Traefik, Caddy or Nginx auth_request ) | An extra container plus its config and session storage, and a second thing to upgrade | If the auth service is down, every page returns 401 or 502, including your own recovery notes | | Identity provider SSO (Cloudflare Access, Google or GitHub OIDC) | No local user database, but a hard dependency on a third party for every page load | Your reader list lives outside your server, and access decisions are logged elsewhere | | Network gate only (WireGuard, Tailscale) | Client software on every device, including phones and a television browser | No gate once a device is on the network, so a borrowed laptop reads everything | | Static platform password (Netlify, Vercel) | Zero server administration | One password per site, and the build output sits on a shared multi-tenant platform | Your hosting choice cuts across this table rather than replacing it. A rented VPS, a home server, a NAS or a Yundera instance can each run any of the first four rows, since all of them are just containers and a proxy in front of a folder. Pick the row you will still maintain in twelve months. What ends up in the build folder that you did not expect to publish? Before you trust the gate, audit what is behind it. The reliable method is to grep the output rather than to reason about the config. Run grep -ri "your-secret-string" build/ after every build, and add it to your deploy script. Anything in customFields : values you put under customFields in docusaurus.config.js are serialised into the
Comments
No comments yet. Start the discussion.