How to Make a Password-Protected Website
You built a website, an internal dashboard, a staging environment, a client preview, or a one-off microsite. You do not want the entire internet to see it. You want the people you share the link with to see it, and nobody else.
That is a password-protected website. The idea is simple; the implementation options are not. This post walks through every real way to put a password on a site in 2026, what each one costs, how secure each one is, and what to reach for depending on what kind of site you have.
TL;DR: five legitimate ways and one anti-pattern
| Method | Works on | Complexity | Cost | Security |
|---|---|---|---|---|
| .htaccess (Apache basic auth) | Sites hosted on Apache | Low | Free | OK (HTTP basic) |
| nginx basic auth | Sites hosted on nginx | Low | Free | OK (HTTP basic) |
| Cloudflare Access | Any site behind Cloudflare | Medium | Free up to 50 users | Strong (Zero Trust) |
| WordPress page password | WordPress sites | Very low | Free (with WP) | Low (per-page only) |
| Platform-enforced password | Static hosts (Netlify, Vercel, Deloc) | Very low | Varies | Strong (edge-enforced) |
| Client-side JS password | Anywhere | Low | Free | Effectively none |
The last one is a trap we’ll come back to at the end.
Method 1: .htaccess and HTTP Basic Auth (Apache)
The traditional answer. If your site runs on an Apache server — shared hosting, a Linode VM running the LAMP stack, cPanel — you can drop an .htaccess file in the site root and get password protection without touching application code.
Create two files.
/var/www/yoursite/.htaccess:
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /var/www/yoursite/.htpasswd
Require valid-user
/var/www/yoursite/.htpasswd (generated with htpasswd -c /path/.htpasswd username):
username:$apr1$abcdef12$Yv5gG2vJ/Nj5KnZ1q0.V4.
Restart Apache or reload config. Visitors to any page under that directory get a native browser prompt asking for a username and password. If they cancel or enter wrong credentials, they get a 401.
Pros: zero application code, works with any site structure, protects every file in the directory tree.
Cons: the credential prompt is the ugly native browser dialog — no branding, no styling, no “forgot password” flow. HTTP Basic Auth sends credentials base64-encoded on every request (safe over HTTPS, insecure over HTTP). Shared credentials are hard to rotate when someone leaves. Not suitable for many users or anything resembling individual identity.
When this is right: you own an Apache server, you want to gate a staging site or internal tool, you are the only admin, and one shared password across your team is fine.
Method 2: nginx basic auth
The nginx equivalent. Same HTTP Basic Auth primitive, different config syntax.
In your nginx site config:
server {
listen 443 ssl;
server_name yoursite.com;
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
root /var/www/yoursite;
index index.html;
}
}
Create the password file with htpasswd -c /etc/nginx/.htpasswd username. Reload nginx.
Pros and cons are identical to the Apache version. Same ugly native prompt, same shared-credential limitation, same “good enough for staging” profile.
When this is right: you run nginx, same constraints as Method 1.
Method 3: Cloudflare Access
If your site is already behind Cloudflare (and a surprising amount of the internet is), Cloudflare Access is a dramatically better option than basic auth. It is a Zero Trust product: instead of a single shared password, you authenticate users via a real identity provider (Google, GitHub, Microsoft, Okta, email OTP) and authorize them via policies.
You do not need an app-level integration. Cloudflare sits between the visitor and your origin, intercepts unauthenticated requests, presents a clean login page, and only forwards the request to your site after the user authenticates.
Set up flow:
- From the Cloudflare dashboard, go to Zero Trust → Access → Applications.
- Add a self-hosted application pointing at
yoursite.com(or a specific subpath likeyoursite.com/staging/*). - Create a policy: “allow users with email in domain
yourcompany.com” or “allow the specific emails in this list.” - Pick the identity providers visitors can authenticate with.
Visitors hit the site, Cloudflare prompts them, they sign in with Google or whatever you enabled, Cloudflare checks the policy, and either lets them through or blocks them.
Pros: strong security, individual identity (not shared creds), clean UI, rotation is trivial (remove a user from the policy), works alongside your existing site with zero code changes. Free for up to 50 users.
Cons: requires Cloudflare in front of your site. Initial policy setup is a 15-minute rabbit hole if you have never used Zero Trust before. Overkill for “let me show my friend this site.”
When this is right: internal tools, staging environments, dashboards shared across a company, anywhere you want individual accountability rather than a shared password.
Method 4: WordPress page passwords (if you’re on WP)
WordPress has a built-in per-page password feature. When editing a post or page, in the sidebar under Visibility, click Edit → Password protected, type a password, publish.
Visitors hit the page, see a prompt for the password, enter it, see the content. WordPress stores the authorization in a cookie so they do not re-enter on subsequent visits.
Pros: zero setup beyond WordPress itself, per-page granularity (some pages public, some gated), simple.
Cons: protects a single post or page, not the whole site. No user management (shared password). If you want to protect the entire WordPress site, you need a plugin like Password Protected or My Private Site.
When this is right: you already use WordPress and want to gate a specific page or two — a client preview, a hidden resource, a draft announcement. For whole-site protection on WP, use a dedicated plugin or put Cloudflare Access in front.
Method 5: Static hosting platform features
If your site is a static build (Vite, Next export, Astro, plain HTML) hosted on a platform, several platforms have password protection built in.
Netlify: Password protection is on Pro plans and above. From the site settings → Build & deploy → Post processing → Password protection, you set a site-wide password. Netlify enforces it at their edge. Simple, but Pro starts at $19/site/month.
Vercel: Password protection is a paid feature (Team plan and above). From the project settings → Deployment Protection, you can enable password for preview deployments or production. Like Netlify, enforced at their edge.
Cloudflare Pages: No built-in password protection on Pages itself. You bolt on Cloudflare Access (Method 3).
GitHub Pages: No password protection. At all. Anything you publish is public.
Deloc: Password protection is built in and edge-enforced. One command:
npx @deloc/cli password my-site
It prompts for a password, stores a bcrypt hash server-side, and a Cloudflare Worker at the Deloc edge checks for a valid session cookie on every request before serving any file. Visitors see a clean branded prompt the first time, enter the password, get a session cookie, and never see the prompt again until the session expires. Remove the password with --remove. Available on every paid tier.
When this is right: your site is a static build, you want the path of least resistance, and you want to avoid standing up any auth infrastructure yourself. If you are already on Netlify or Vercel Pro, use theirs. If you are new and looking for a static host that includes password protection without bumping you to a team plan, Deloc’s free-through-Pro tiers include it.
The anti-pattern: client-side JavaScript passwords
You will find tutorials for this. Do not follow them.
The idea: you write a small bit of JavaScript that prompts for a password on page load, checks it against a hardcoded value, and either reveals the content or redirects. Something like:
<script>
const pw = prompt("Password:");
if (pw !== "hunter2") window.location = "https://google.com";
</script>
<div id="secret-content">
<!-- the "protected" page content -->
</div>
This is not password protection. It is a speed bump. The actual content is already sitting in the HTML source of the page. Anyone who:
- Views page source (Ctrl-U)
- Opens DevTools (F12)
- Disables JavaScript
- Clicks “cancel” fast enough
…sees the content without the password. The password itself is in the JavaScript, readable by anyone.
Variations that encrypt the content client-side (staticrypt and similar) are slightly better because the content is not in the source, but the decryption key derives from the password and the hash is in the HTML, so an attacker can run an offline dictionary attack at whatever speed their hardware allows. For a weak password, this takes seconds.
When is this acceptable? Only for purely social, non-sensitive gating — a wedding invite page, an April Fools joke, a puzzle. If the content mattering is the reason you want the password, use any of the other five methods instead.
Picking the right method
A quick decision tree:
“My site is a Vite/Next/Astro build, hosted on a static platform.” Use your platform’s built-in password feature (Method 5). If you are shopping for a platform, any of Netlify Pro, Vercel Team, or Deloc will do this out of the box. Deloc is cheapest for small teams because password protection is on paid tiers starting at $10/month.
“My site runs on Apache or nginx on a server I control.” Use Method 1 or Method 2 for a single shared password. Use Method 3 (Cloudflare Access) if you want individual identities and real user management.
“My site is WordPress.” Page-level: Method 4. Whole-site: a plugin like Password Protected, or put Cloudflare Access in front.
“My site is a SaaS application where every user has their own account.” Password protection as discussed here is the wrong primitive. You need a real auth system — Clerk, Auth0, Supabase Auth, or roll your own with Lucia. That is outside the scope of this post.
“I want to protect a single deploy — a dashboard I built in Claude Code, a client preview, a Figma-to-code export.” Deloc’s edge-enforced password is the fastest path. Build, deploy, set password, share. Total time from zero to protected URL: about two minutes.
A concrete walkthrough: password-protecting a static dashboard
If you already have a static site locally — built in Claude Code, Cursor, Vite, or hand-rolled — here is the end-to-end flow using Deloc, start to finish.
-
Install the Deloc CLI (one-time):
npx @deloc/cli loginThis opens a browser, you sign in with Google or Microsoft, and a token is stored at
~/.deloc/config.json. -
Deploy your site:
npx @deloc/cli deploy --name "client-preview"The CLI detects your build, uploads the output, prints a URL like
https://client-preview--your-org.deloc.app. -
Set a password:
npx @deloc/cli password client-previewEnter a password at the prompt. Confirm. Done.
-
Share the URL and the password through different channels. Recipients hit the URL, get a branded prompt, enter the password, see the site. Their session cookie lasts long enough that they will not be re-prompted on the same browser for hours.
-
When you no longer need the password, remove it:
npx @deloc/cli password client-preview --remove
The protection is enforced at Cloudflare’s edge before any file is served. The password is never in the page source. The content is never exposed to anyone without the password.
What security does each method actually give you?
Worth being precise, because this is the part most tutorials gloss over.
HTTP Basic Auth (.htaccess, nginx) — as strong as the password and TLS. Safe over HTTPS. One shared credential, limited to one or a few users. No account recovery.
Cloudflare Access — as strong as the underlying identity provider. If you require Google SSO, the security is Google’s login security (2FA, device verification). Individual identity, policy-driven, auditable.
WordPress page passwords — just HTTP Basic Auth in a prettier wrapper. Single shared password per page.
Static platform password (Netlify, Vercel, Deloc) — edge-enforced, bcrypt-hashed password stored server-side, session cookie issued after successful auth. Functionally equivalent to HTTP Basic Auth in security terms, better UX.
Client-side JS password — not security. The content is accessible to anyone who can View Source.
For most “I want to share this with specific people and nobody else” use cases, the static platform password option (Method 5) hits the right spot: strong enough, simple enough, takes under a minute to set up.
Wrap up
Five real ways to password-protect a website, in descending order of effort: Cloudflare Access for team-scale access with real identity, HTTP Basic Auth via .htaccess or nginx for a server you control, a platform-enforced password for a static site, WordPress’s built-in page password if you are on WP. One anti-pattern — client-side JavaScript passwords — that is not actually protection and should be avoided.
If your use case is a static site and you want the fastest path, Deloc’s built-in password does the job in one command. If you need real user management and auditing, Cloudflare Access is a better answer. Match the method to the situation and skip the ones that do not fit.
For related reading, see How to Share a Dashboard Built with Claude for the full deploy-and-share workflow, or Build an Auto-Refreshing Dashboard with React, BigQuery, and Deloc for the complete setup including scheduled data updates.