Cloudflare Pages has no password toggle. Search for how to password protect Cloudflare Pages and you get two answers: write your own middleware in Pages Functions, or set up Cloudflare Access in the Zero Trust dashboard. Both work. Neither is "add a password to /investors and move on."

seite v0.18 made it a config change. Mark a collection private = true, add an [access] section to seite.toml, run seite access set-password, deploy. seite generates the Cloudflare Worker, keeps the gated pages out of your sitemap and llms.txt, and stages the password as a Cloudflare secret so it never touches your repo.

This post covers the three ways to gate a Pages site and when each one fits. It explains the difference between private and password-protected (people mix them up constantly) and walks through the full setup: one path, several passwords, then a gated trust center on its own subdomain. It ends with the limits, including the cases where Cloudflare Access is the better tool.

Can You Password Protect Cloudflare Pages? Three Options

Not out of the box. Cloudflare Pages has no standard shared-password setting, and the top-ranking guides say so up front. Here's what people use instead.

1. Pages Functions middleware. The most-linked answer is Charca's cloudflare-pages-auth: copy a functions/ directory into your project, set a CFP_PASSWORD variable in the dashboard, get a login form and a cookie. It's a good template.

It also gives you one password for the whole project, and you now own that auth code. Other guides use HTTP Basic Auth with the credentials hardcoded in the Worker source, which means the password lives in git.

2. Cloudflare Access. Zero Trust puts an identity check in front of your hostname: one-time PIN by email, Google, GitHub, Okta. It's the right tool for real user identity, and the free plan has a 50-user limit.

It isn't a shared password, though. Every visitor needs an allowed email or identity, and you configure it per hostname in a separate dashboard.

3. seite's built-in password access. Config in seite.toml, a generated Worker, one password per group. Different paths or subdomains can use different passwords.

Functions middlewareCloudflare Accessseite [access]
SetupCopy code, set env varZero Trust dashboard policiesseite.toml + one command
CredentialOne shared passwordPer-user identity (email, SSO)Shared password per group
Multiple passwordsWrite it yourselfN/A (policies per user/group)Built in (access_group)
Protect a subdomainSeparate copy per projectYes, per hostnameYes, private + subdomain
Keeps pages out of sitemap/feedsNoNoYes
Audit who visitedNoYesNo

That second-to-last row matters more than it looks. Middleware blocks the page, but your static site generator still lists it in sitemap.xml, RSS and llms.txt. The title and URL of your "Q3 investor update" end up in public files.

Private vs Password-Protected: Two Different Switches

seite has two settings that sound alike and do different jobs.

private = true hides a collection. Every page still builds, including the collection's hub page. What changes is discovery: the pages are excluded from the homepage listing, sitemap.xml, llms.txt, llms-full.txt, search-index.json, RSS/Atom feeds and tag pages, and every page gets <meta name="robots" content="noindex, nofollow">. The build log tells you how many pages it held back:

ℹ 1 private pages excluded from discovery

[access] locks it. Add an [access] section and every private = true collection sits behind a password on Cloudflare Pages.

SettingHidden from sitemap, llms.txt, search, feedsnoindex, nofollowRequires a password
private = true aloneYesYesNo
private = true + [access]YesYesYes (Cloudflare Pages)
Warning

private = true without [access] is not access control. The HTML is still publicly fetchable by anyone who has the URL. It's the right setting when something else does the gating (Cloudflare Access, HTTP auth at another layer), and the wrong one if you think you've locked the door.

The split is deliberate. v0.15 shipped private as discovery-only for sites that already sit behind Cloudflare Access, and v0.18 added [access] on top without changing what existing private configs do. The full field reference is in the private collections configuration.

How to Password Protect Cloudflare Pages with seite

This assumes your site already deploys to Cloudflare Pages with seite deploy. If it doesn't, start with deploying a static site to Cloudflare Pages and come back. You'll need Wrangler installed and logged in, since seite uses it to upload secrets.

The example: a startup site with a public blog and an /investors section for board updates.

1. Mark the Collection Private

[deploy]
target = "cloudflare"
project = "acme-site"

[[collections]]
name = "investors"
label = "Investors"
directory = "investors"
url_prefix = "/investors"
default_template = "page.html"
private = true

Markdown goes in content/investors/ like any other collection.

2. Enable Password Access

[access]
mode = "password"
session_hours = 168

mode = "password" is currently the only mode. session_hours controls how long a browser stays logged in: the default is 168 (one week), and any value from 1 to 8760 is accepted.

Each private collection gets a password group. It defaults to the collection's name, so this one is investors.

3. Check What's Protected

seite access groups
GROUP                SCOPES                         CLOUDFLARE PROJECTS
----------------------------------------------------------------------------
investors            path /investors                acme-site

This is the map the Worker will enforce. /investors, everything under it and the .md alternates of those pages all require the investors password. If a group shows (project not configured), set deploy.project (or deploy_project for a subdomain) first.

4. Set the Password

seite access set-password investors
Password for access group 'investors': 
Confirm password: 
✓ Stored password for 'investors' on Cloudflare Pages project 'acme-site' (production and preview)

The password is prompted twice and piped to wrangler pages secret put over stdin. It is never read from a flag, written to seite.toml or printed.

seite also generates a random session-signing secret per group. Both are staged for production and preview, so staging deploys are gated too. If only one group exists, you can drop the group name.

5. Deploy

seite deploy

Staged secrets apply to the next deployment, so the password isn't live until you deploy. Deploy from the Pages project's production branch (main for projects seite created) and run seite deploy --preview to activate it on preview. Visitors to /investors now get a login form. The rest of the site stays public.

One setting to change in the Cloudflare dashboard while you're there: on the Pages project, open Settings > Runtime and set Fail closed. Every request on a password-enabled project runs the Worker, and in Fail open mode Cloudflare serves static files without the Worker once your Workers request limit is reached. More on that in the limits below.

The password-protecting paths and subdomains section of the deployment guide has the same flow in reference form.

One Site, Several Passwords

Most DIY guides stop at one password. Real sites need more. Investors shouldn't share a password with customers, and a partner portal shouldn't unlock your internal docs.

access_group handles that. Collections with the same group share a password and a login session. Collections with different groups get independent ones:

[[collections]]
name = "investors"
label = "Investors"
directory = "investors"
url_prefix = "/investors"
default_template = "page.html"
private = true
access_group = "board"

[[collections]]
name = "handbook"
label = "Handbook"
directory = "handbook"
url_prefix = "/handbook"
default_template = "doc.html"
private = true
access_group = "staff"

[[collections]]
name = "runbooks"
label = "Runbooks"
directory = "runbooks"
url_prefix = "/runbooks"
default_template = "doc.html"
private = true
access_group = "staff"

Board members get one password. Staff get another that opens both /handbook and /runbooks. Run seite access set-password once per group.

A few rules keep this predictable:

  • An empty url_prefix protects the whole domain. Use it for a fully private site.
  • When scopes overlap, the most-specific path wins. A private site at / with a separately gated /board uses the board password for /board.
  • Translations are covered. If you have [languages.es], /es/investors is protected with the same group.
  • Conflicts fail the build. Two collections that assign the same path to different groups are a config error, not a silent override.
  • Group names can use ASCII letters, numbers, _ and -.

Files need gating too. Anything under static/ is public. With [access] enabled, put protected downloads (the pitch deck PDF, say) in static/private/<group>/. They're served from /private-assets/<group>/ behind that group's password.

Password-Protect a Subdomain: A Gated Trust Center

Some content belongs on its own hostname. The common case is a security trust center at trust.example.com that customers under NDA can read but the public can't.

seite's trust collection preset already deploys to a subdomain. Add private and a group:

[[collections]]
name = "trust"
label = "Trust Center"
directory = "trust"
url_prefix = "/trust"
nested = true
default_template = "trust-item.html"
private = true
access_group = "customers"
subdomain = "trust"
subdomain_base_url = "https://trust.example.com"
deploy_project = "acme-trust"

A private collection with subdomain protects the entire subdomain output, not a path inside it. seite access groups shows it that way:

GROUP                SCOPES                         CLOUDFLARE PROJECTS
----------------------------------------------------------------------------
customers            subdomain trust (entire site)  acme-trust
investors            path /investors                acme-site

The order matters: seite deploy --setup creates the acme-trust Pages project, then seite access set-password customers uploads its secrets, then seite deploy activates it. Attaching trust.example.com to that project is a one-time step in the Cloudflare dashboard (Custom domains), because seite only auto-attaches the main site's domain. The trust center documentation covers the certifications, subprocessors and FAQ data that fill the hub.

The same pattern works for docs.example.com as internal documentation, or a partners. subdomain.

What Happens at the Edge

With [access] on, seite build writes two files into the output:

  • _worker.js, an advanced-mode Pages Worker with your route table baked in.
  • _routes.json, which sends every request (/*) through that Worker.

On a protected path without a valid session, the Worker serves a small login form (not a Basic Auth browser prompt). A correct password sets a __Host- session cookie marked Secure, HttpOnly and SameSite=Lax, signed with HMAC-SHA256 using the group's secret and valid for session_hours. Password checks use a constant-time comparison, and the login page is served with Cache-Control: no-store and X-Robots-Tag: noindex, nofollow.

It fails closed. If a group's secrets are missing (say you deployed before running set-password), protected paths return a 503 "Access unavailable" page instead of the content. The Worker decodes each path before matching it, so encoded separators can't slip past a protected route, and it rejects .. segments outright.

seite owns both generated files. If your public/ directory already has a custom _worker.js or _routes.json, the build fails rather than guess which one should win.

Limits and When Cloudflare Access Is the Better Fit

This is a shared-password gate for a static site. Know where it stops.

  • Cloudflare Pages only. Deploy pre-flight rejects password-enabled sites targeting GitHub Pages or Netlify, and set-password refuses to run for them.
  • No per-user identity. Everyone in a group types the same password. There's no record of who visited and no way to revoke one person without changing the password for all. If you need SSO, per-user access or an audit trail, use Cloudflare Access. In that setup, keep private = true without [access] so the content stays out of discovery files, and let Access do the gating.
  • Rotation logs everyone out. Running set-password again creates a new signing secret. The next deploy invalidates every existing session in that environment.
  • Nothing is live until you deploy. Staged secrets apply to the next deployment only.
  • Set it from a terminal. set-password needs an interactive terminal and refuses to read passwords from flags, so it can't run unattended in CI.
  • No login rate limiting. The Worker doesn't throttle attempts. Use a long, random password.
  • Every request runs the Worker. Because _routes.json includes /*, all requests on that project invoke the Worker, and Workers Free allows 100,000 requests per day. Check Settings > Runtime > Fail open / closed on the Pages project and pick Fail closed. Fail open serves static assets without running the Worker once the limit is hit.

For investor pages, internal docs, client previews and a gated trust center, a shared password per group is usually the right amount of security. For anything regulated, pair seite's discovery controls with Access.

FAQ

Does Cloudflare Pages Have Built-In Password Protection?

No. Cloudflare Pages has no shared-password setting. You can restrict preview deployments with Cloudflare Access, write Pages Functions middleware or use a tool that generates the Worker for you. seite does the last one from seite.toml with [access] and private = true.

Is Cloudflare Access Free?

Cloudflare's Zero Trust free plan includes Access with a 50-user limit. It authenticates people by email one-time PIN or an identity provider rather than a shared password. That's better for audit and per-user revocation, and more setup than a single password for a handful of investors.

Can I Password Protect Only One Page or Folder?

Yes. Password protection in seite applies per collection, so put the gated pages in their own collection with its own url_prefix (for example /investors) and mark it private = true. Everything outside that prefix stays public. Protected downloads go in static/private/<group>/.

Does private = true Password Protect My Pages?

Not on its own. private = true removes pages from the sitemap, llms.txt, search index, feeds and homepage and adds noindex, nofollow, but anyone with the URL can still load them. Add an [access] section to require a password on Cloudflare Pages.

How Do I Change the Password?

Run seite access set-password <group> again, then deploy. The new password takes effect with that deployment, and sessions signed with the old secret stop working, so everyone in that group logs in again. Other groups are unaffected.

What About StatiCrypt or Client-Side Encryption?

StatiCrypt-style tools encrypt the HTML and decrypt it in the browser with JavaScript. They work on any host, including GitHub Pages. seite checks the password at Cloudflare's edge before serving anything, so protected files never reach an unauthenticated browser (with the Pages project set to fail closed), and there's no client-side decryption step.

Gate It, Then Get Back to Shipping

Password protecting Cloudflare Pages shouldn't mean owning auth code. With seite it's five steps:

  1. Mark the collection private = true.
  2. Add [access] with mode = "password".
  3. Check the scopes with seite access groups.
  4. Upload each group's password with seite access set-password.
  5. Deploy.

Use access_group when different audiences need different passwords, and subdomain when a whole hostname should be gated. If you're building the rest of a company site around it, the guide to a static site generator for startups covers the landing page, docs and changelog that sit next to your investor pages. Every seite access flag is in the CLI reference.