Skip to content
All guides

Vercel and Netlify preview deployments are public: how to lock them down

By Obscura Security · Updated · Vercel, Netlify, Next.js, Supabase

Short answer

Every branch or pull request you push to Vercel or Netlify gets its own live URL, and unless you protect it, anyone who finds the URL can use it, often against your production database and keys. Turn on deployment protection for previews, give Preview its own environment variables and a separate database with fake data, and add noindex.

When you push a branch to a project hosted on Vercel or Netlify, the host builds it and puts it online at its own address. Those preview deployments are meant for you and your team, but unless you’ve protected them they’re open to anyone with the link.

Why this happens

Previews exist so you can test a change before it goes live. They behave like the real app, which is why they’re useful, and also why they’re risky:

  • They run unfinished code. A branch might have an admin page with no login check yet, a debug route that prints environment details, or a flag that skips authentication “while I test”.
  • They often use production settings. When you add an environment variable in your host’s dashboard, it’s easy to leave every environment ticked. The preview then connects to your production database with your real keys.
  • The addresses leak. Preview URLs show up in pull request comments, commit statuses, chat messages, screenshots and browser history, and search engines can index them.
  • Old ones stay online. A preview from three months ago is still reachable at its address until it is deleted, along with whatever bugs it had.

An AI tool that creates a branch for every experiment can leave a long trail of these.

How to check your own app

  1. Open a preview in a private window. Take a recent preview URL from your dashboard and open it in a private or incognito window where you’re not logged in to Vercel or Netlify. If you see your app instead of a login page, previews are public.
  2. Check the protection setting. On Vercel, open your project, then Settings, then Deployment Protection. See whether Vercel Authentication (or another method) covers preview deployments. On Netlify, look through your site’s settings for access or visitor protection options.
  3. Review environment variables. Open Settings, then Environment Variables. For each variable, see which environments it applies to. Look for production database URLs, the Supabase service role key, live Stripe keys and other secrets that have Preview ticked.
  4. Search your code for bypasses. Look for flags such as SKIP_AUTH, DISABLE_AUTH, DEBUG or if (process.env.NODE_ENV !== 'production') around authentication or admin checks. On a preview, NODE_ENV is production for a Next.js build, but other flags might still be set.
  5. See what search engines know. Search for site:vercel.app along with your project name or a phrase from your app, and do the same for netlify.app.
  6. List old deployments. In the Deployments tab, look for previews of branches you no longer use.

The fix

1. Protect previews

On Vercel, go to Settings, then Deployment Protection, and enable Vercel Authentication for preview deployments. Visitors then have to be logged in to a Vercel account with access to the project or team. Password protection and IP allow lists are also offered, with plan limits that change over time.

If a client needs to see a preview, share it through Vercel’s sharing options or add them to the team, so you don’t have to turn protection off for everyone. On Netlify, use the password or visitor protection options your plan offers, and check which deploy contexts they cover.

2. Give Preview its own environment variables

In your host’s environment variable settings, set each variable only for the environments that need it. Production secrets belong to Production only. For Preview, use:

  • a separate database (a second Supabase project works, as does a branch or copy filled with fake data)
  • test-mode keys for Stripe and similar services
  • a different email provider setup or a sandbox that can’t message real customers
  • no service role key, unless the preview database holds nothing real

On Netlify, a variable can have a different value for each deploy context, such as Production, Deploy Previews and Branch deploys.

Changing a variable only affects new deployments. Existing previews keep the values they were built with. If a production secret was ever in the Preview environment, treat it as exposed and rotate it. See secrets committed to GitHub for the order to do that in.

3. Keep authentication bypasses on your own machine

If you need a way to skip login while developing, make it impossible to switch on anywhere else:

// Only true when running locally with the dev server.
const skipAuth =
  process.env.NODE_ENV === 'development' && process.env.DEV_SKIP_AUTH === 'true'

Better still, don’t build the bypass. Create a test user with a known password in your preview database.

4. Add noindex outside production

This stops search engines listing your previews. Vercel documents that it sends a noindex header on preview deployments, but adding your own covers custom preview domains and Netlify too. In next.config.js:

// VERCEL_ENV is 'production', 'preview' or 'development' on Vercel.
module.exports = {
  async headers() {
    if (process.env.VERCEL_ENV === 'production') return []
    return [
      {
        source: '/:path*',
        headers: [{ key: 'X-Robots-Tag', value: 'noindex, nofollow' }],
      },
    ]
  },
}

Noindex only affects search engines. It doesn’t keep anyone out, so it supplements the protection in step 1 and doesn’t replace it.

5. Clean up

Delete preview deployments you don’t need from the Deployments tab, and close branches you’ve abandoned. Check whether your project has a setting for how long old deployments are kept.

6. Test again

Open a fresh preview URL in a private window. You should land on a login screen. Then confirm that the preview app’s database is not your production one by creating a test record in the preview and checking that it doesn’t appear in production.

Mistakes to avoid

  • Ticking every environment when adding a secret. It takes one extra click to scope it, and prevents most of this problem.
  • Turning protection off so one person can see a preview, then forgetting to turn it back on.
  • Using the same OAuth app, webhook endpoint or email sender for previews and production. A preview can send real emails or process real events.
  • Relying on a hard-to-guess URL. Preview addresses are visible in too many places to count as secret.
  • Assuming a preview with a login page is safe when its API routes are open. Test the API addresses too, as covered in admin page only hidden in the UI.

Common questions

Can anyone find my preview URL?
Preview URLs follow a predictable pattern based on your project and branch names, they appear in pull request comments and commit statuses, and they can be shared by accident. They also get indexed by search engines if nothing tells crawlers not to. Don't count on the address staying private.
Does Vercel protect preview deployments by default?
On newer projects, Vercel Authentication is usually switched on for preview deployments. Older projects, and projects where someone changed the setting, may not have it. Check Deployment Protection in your project settings instead of assuming.
Do I need a paid plan to protect previews?
The basic option, requiring a Vercel login with access to the project, is available on most plans. Extras like password protection or IP allow lists usually need a higher plan or an add-on, and plans change, so check the current pricing page.
Should previews use my production database?
No. A preview runs unfinished code. If it points at production, a bug or a missing permission check on a branch can read, change or delete real customer data. Use a separate database with fake data.
What about Netlify deploy previews?
They behave the same way: a public URL per pull request or branch. Netlify lets you set environment variables per deploy context and offers visitor access controls on some plans. Look at both in your site settings.

Written by Obscura Security, an Australian practice that checks web apps for problems like this one. If you want a second pair of eyes on yours, email [email protected].