Ship a preview deployment on Vercel's free tier and it looks protected — there's a wall between the URL and the internet. Send that link to a client for review, though, and they'll hit the wall too. Vercel's free protection method, Vercel Authentication, only lets in people who are already logged into your Vercel team. Your client isn't on your Vercel team. To let them in with nothing more than a password you both agree on, you need a feature Vercel sells separately: Advanced Deployment Protection, $150 a month, stacked on top of the $20-a-seat Pro plan you're already paying for.
That's $1,800 a year for a capability that amounts to one if statement in front of a URL — checking a password before a request reaches your app. Below is the actual config that does it on a self-hosted platform, why it costs nothing marginal to add, and the honest gaps between "password on a URL" and what Vercel's $150/month actually buys.
What "Free" Protection Actually Covers — and Where It Stops
Vercel's deployment protection splits into two independent choices: a protection method (how you verify a visitor) and a protection scope (which URLs get gated). The free scope, Standard Protection, covers every preview and deployment URL — that part costs nothing on any plan.
The method is where the free tier runs out. Vercel Authentication is available everywhere, including Hobby, but it only recognizes people already signed into your Vercel account. A client, a designer on a different tool, an external QA contractor — none of them have that. To let someone in with just a shared password, Vercel's own docs are explicit: Password Protection is "available on the Enterprise plan, or as a paid add-on for Pro plans."
The gap widens further down the plan ladder: on Hobby, there's no way to password-protect the production domain at all — only Pro and Enterprise even offer the "All Deployments" scope needed to cover it, and Password Protection layered on top of that scope still requires the same $150/month add-on. A solo developer wanting to keep a client's staging site off Google's index has no free path to a password gate on Vercel; a self-hosted platform that treats auth as an ingress primitive doesn't draw that line by plan tier.
That add-on is Advanced Deployment Protection, and it isn't priced à la carte. Enabling it unlocks a fixed bundle:
| Feature | What it does | Do you need it for "let a client review a preview link"? |
|---|---|---|
| Password Protection | Gate any URL behind a shared password | Yes — this is the actual ask |
| Private Production Deployments | Password-gate the live production domain too | No, unless you're also hiding prod |
| Deployment Protection Exceptions | Per-deployment allowlist rules | No, not for a one-off review link |
You can't buy just the first row. The $150/month is workspace-wide (per community reports, not per-project) and comes with a 30-day minimum commitment before you're even allowed to turn it back off. A team that wants nothing more than "don't let this review URL leak" pays for two features it didn't ask for, on a billing cycle it can't exit for a month.
The Actual Config: Gating a Preview URL for $0 Marginal Cost
Here's the part Vercel's pricing page doesn't want to be compared against: password-gating a URL is not novel infrastructure. It's HTTP Basic Auth, a feature every reverse proxy has shipped since before "serverless" was a word. A self-hosted PaaS that already terminates TLS and routes each deploy to its own subdomain is one directive away from gating it.
If your ingress is nginx, per-deployment auth looks like this:
# /etc/nginx/conf.d/preview-myapp-pr-42.conf
server {
listen 443 ssl;
server_name pr-42.myapp.preview.example.com;
auth_basic "Preview environment";
auth_basic_user_file /etc/nginx/previews/pr-42.htpasswd;
location / {
proxy_pass http://myapp-pr-42-service:8080;
proxy_set_header Host $host;
}
}Generating the credential file is one command, run automatically when the preview environment is provisioned:
htpasswd -Bbc /etc/nginx/previews/pr-42.htpasswd reviewer "$(openssl rand -base64 12)"If your ingress runs on Kubernetes — the more likely case for a Cluster API–managed fleet — the same guarantee is a two-resource pattern: a Secret holding the htpasswd file, and an annotation on the Ingress (or an equivalent HTTPRoute filter on Gateway API) referencing it:
apiVersion: v1
kind: Secret
metadata:
name: pr-42-preview-auth
type: Opaque
data:
auth: <base64-encoded htpasswd content>
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: pr-42-preview
annotations:
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: pr-42-preview-auth
nginx.ingress.kubernetes.io/auth-realm: "Preview environment"
spec:
rules:
- host: pr-42.myapp.preview.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-pr-42-service
port:
number: 8080Both blocks generate from the same inputs a preview pipeline already has on hand — the branch name, the subdomain, and a random password — at deploy time, with no separate product to buy, no separate invoice, and no 30-day lock-in to cancel. The marginal cost is the few milliseconds nginx spends checking an Authorization header, because the TLS termination and request routing this auth check piggybacks on were already happening for every request regardless.
Making the Password Part of the Deploy, Not a Manual Step
A config block that has to be hand-written per pull request isn't actually cheaper than Vercel's toggle — it just moves the cost from a monthly invoice to an engineer's afternoon. The point only holds if generating and delivering the password is a byproduct of the deploy pipeline itself, the same way the subdomain and the TLS cert already are.
That's a small addition to whatever already provisions the preview environment: a CI step or a controller reconcile loop that, on every new deploy, generates a random password, writes the htpasswd secret, and posts the credential back to the pull request as a comment — the same place a reviewer already goes to find the preview link. On teardown (PR merged or closed), the same automation deletes the Secret and the Ingress/HTTPRoute alongside the rest of the namespace, so a stale credential never outlives the environment it guards. None of this is new infrastructure for a platform that's already namespace-per-PR; it's three more lines in a reconciler that's already watching for PR open/close events, generating a password with the same openssl rand call used above.
What You're Giving Up Against Vercel's Version
It would be dishonest to stop there and call it a wash. Vercel's Password Protection isn't implemented as HTTP Basic Auth — it sets a JWT in a cookie, scoped to the specific deployment URL, so a visitor enters the password once and stays in until the deployment changes or the password rotates. Basic Auth, by contrast, re-prompts per browser session and doesn't distinguish "the user typed the right password" from "the user's browser cached credentials it shouldn't still have" once a password rotates — you have to bounce the credential file and rely on the browser dropping stale creds, which is coarser than a cookie you can invalidate server-side.
The second real gap is automation. Vercel's docs list two built-in bypass paths — Shareable Links and Protection Bypass for Automation — specifically so a CI job (a Lighthouse audit, an E2E test suite hitting the preview URL) can reach a protected deployment without a human typing a password into a headless browser. Raw Basic Auth doesn't ship that distinction for free; every caller looks the same to it.
The fix is cheap but not zero-effort: carve out a bypass with the same primitive you already have. An IP allowlist for your CI runners' egress range, or a second, longer-lived "automation" credential injected as a CI secret and never rotated on the reviewer-facing cadence, both close the gap using the identical auth_basic/Ingress annotation pattern above — no new product, just a second htpasswd entry or a satisfy any block combining IP and credential checks. It's not as polished as a dashboard toggle, but it's a config change, not a subscription.
The Number That Actually Matters
| Vercel Advanced Deployment Protection | Self-hosted Basic Auth | |
|---|---|---|
| Price | $150/month ($1,800/year) | $0 marginal — reuses existing ingress |
| Commitment | 30-day minimum before disabling | None |
| What you get | Password Protection + Private Production + Exceptions (bundled) | Password Protection only, built to spec |
| Session model | Per-URL JWT cookie | Browser-cached Basic Auth (coarser) |
| Automation bypass | Built-in (Shareable Links, Protection Bypass) | DIY — IP allowlist or second credential |
| Setup | Toggle in dashboard | ~10 lines of ingress config, generated at deploy time |
$1,800 a year buys convenience — a polished dashboard toggle, a session model that doesn't leak stale credentials on rotation, and an automation-bypass path you don't have to build. It does not buy a capability Basic Auth structurally can't provide; every gap in the table above is a config difference, not a missing primitive. For a platform whose ingress already does TLS termination and per-deploy routing for every request, closing those gaps with a second htpasswd line and an IP allowlist is a cheaper trade than a metered add-on with a 30-day exit clause, for teams that would rather own the primitive than rent the checkbox.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with preview environments that gate on a password by default instead of a separate line item. Star the repo on GitHub or deploy your first app today.



