security

How to Password Protect a Static Website

By PreviewSend Team · Jul 17, 2026 · 6 min read

The obvious move, when you need a password in front of a static site, is to drop a password prompt into the page. It feels like the fix: a login screen, a check, and the content behind it. It doesn't work, and it is worth understanding why before you pick an approach that does.

The problem is structural. A static site is a folder of HTML, CSS, and JavaScript, perhaps built by Vite, Astro, or an AI builder. It has no backend of its own. The host hands those files to whoever asks. A real password has to live in the serving layer, where it can decide whether to hand the files over at all.

Why a JavaScript password form isn't access control

Here's the tempting version: a login screen, an onSubmit that compares the typed value to a password, and a redirect to the real content if it matches.

<script>
  if (prompt('Password?') !== 'letmein') location.href = 'about:blank'
</script>

It looks like a gate. It isn't one. By the time that script runs, the browser already has the HTML and the script with 'letmein' sitting in plain text. A visitor doesn't even need to guess. They can open the network tab, view source, or request another unguarded page directly. The check runs after delivery, and delivery is what you were trying to prevent.

Writing the check more cleverly does not help. Obfuscating the password, hashing it in the browser, or splitting it across files leaves the client with everything it needs. Client-side code can improve the experience, but it cannot decide who receives the files. Access control has to happen on the server before the response is sent.

noindex and a hidden URL are not passwords

The other common substitute for a password is hoping nobody finds the link. It shows up in two forms: a noindex directive, and a long unguessable URL.

noindex is a real and useful tool, but it answers a different question. Per Google's documentation on blocking indexing, a noindex rule asks supporting search engines not to include a page after they crawl it. The crawler has to be able to reach the page to see the rule, which is why putting noindex in robots.txt is unsupported and does nothing. Search visibility is not access control. A page that is absent from search can still open instantly for anyone who has, guesses, or receives the URL.

An unlisted URL has the same limitation. It makes the page harder to find, but it authenticates no one. Links end up in browser histories and forwarded messages. Once a link has traveled, there's no password to stop the next person. Obscurity and indexing control reduce who stumbles onto a page, not who is allowed in. When the task is "only these people," authentication has to be a separate layer.

Option 1: Turn on your host's password protection

The least work, if your host offers it, is a password feature built into the platform. The host checks the credential before serving any file, which is exactly where the check belongs.

If you already deploy to one of these, use the protection that is already there. The exact options vary by platform and plan. A shared site password is a blunt instrument: everyone uses the same secret, and rotating it means telling everyone the new one.

Option 2: HTTP Basic authentication

If you control the server that hosts the files, such as nginx, Apache, or a small worker in front of a bucket, HTTP Basic auth is the standard minimal gate. When a request arrives without credentials, the server answers with a 401 status and a WWW-Authenticate header. The browser shows its native username-and-password box and retries with an Authorization header. The MDN guide to HTTP authentication walks through the full exchange.

The one thing you cannot skip: serve it over HTTPS. Basic auth encodes the credentials; it does not encrypt them. On a plain HTTP connection they are readable in transit. TLS protects the exchange, and MDN is explicit that Basic auth is insecure without it. A typical static host gives you no server to configure, so Basic auth is only an option when you own the serving layer.

Option 3: A built-in project password

Most people with a static bundle do not have a server to configure and do not want to run one. On PreviewSend, the gate lives on the project, so you never touch your files.

Upload the site as a ZIP, then set a password in the project settings. If the upload part is new to you, the walkthrough on turning a folder into a live URL covers the structure. After that, the hosted URL serves a branded challenge screen instead of the site. Enter the password once and a cookie carries you through. Get it wrong and you stay on the challenge.

Upload the static site and get a stable preview link

Starting from loose index.html, styles.css, and script.js files? Prepare and host the folder first. The password is a project setting, so it doesn't change how those files link to one another.

Add a server-side password before sharing the preview

Checked against PreviewSend's serving code, not just its docs. The project setting is stored on the server as a salted PBKDF2 hash (src/lib/password.ts). After a correct entry, the submitted password is kept for eight hours in a Secure, httpOnly, SameSite=Lax browser cookie and re-verified against that hash on later requests (src/lib/preview/serve.ts). Use a unique project-specific password rather than reusing an account credential. An unauthenticated request gets the challenge instead of the hosted files, while an empty or incorrect password gets a 401. This is a shared server-side gate for preview links, not an identity system for end users.

Keep the two controls separate. The password decides who can receive the site. PreviewSend also adds X-Robots-Tag: noindex, nofollow to normal site responses and password challenges. Active projects without a password serve a robots.txt disallow; on password-protected projects, unauthenticated requests to any path, including /robots.txt, receive the noindex challenge instead. These directives address discovery, while the password addresses access. You can start a project on the free plan and add a password before sharing. For the rest of the handoff, the client-preview guide covers preflight, messaging, and updates.

If the site came out of an AI builder rather than a hand-written folder, the export step differs but the gate doesn't. Putting a Claude artifact behind a password applies the same idea to a single generated HTML file.

Choose based on where the files live

If the site already lives on Netlify or Vercel, use that platform's deployment protection.

On a server you control, HTTP Basic auth over HTTPS is a small, standard gate.

For a folder of files with no server, use a host with a project-level password. The host does the checking and leaves your files untouched.

The option that looked easiest at the start still isn't one: a password checked in the page's own JavaScript. Whatever else you choose, make sure the decision to hand over the files happens before they're handed over. That's what separates a real gate from a screen anyone can walk around.

FAQ

Can't I just add a password check in JavaScript?

No. By the time your JavaScript runs, the browser already has the page and the script that checks the password. Anyone can open the network tab or view source, read the password out of the code, or request an unguarded file directly. A client-side form hides the page from a casual visitor, not from anyone who looks.

Does a noindex tag keep my site private?

No. noindex asks search engines not to list the page, but the page still opens for anyone who has the URL. That addresses discoverability, not access. Authentication is a separate control.

Is HTTP Basic authentication safe?

Only over HTTPS. Basic auth encodes the username and password in a header, but encoding is not encryption — on plain HTTP those credentials are readable in transit. Served over TLS it is a legitimate, if minimal, gate.

Do I need to change my site to add a PreviewSend password?

No. The password lives on the project, not in your files. Upload the same static bundle, toggle a password in the settings, and the hosted URL starts serving a challenge screen before it serves the site.

Have a site to host? Upload it and get a live link in seconds.

Get it free

← Back to the blog