Skip to content
RefinoStart free

Environment variables and secrets for website owners

You got the repository. You connected a new host. The site built, then fell over: no database, no sign-in, no email. Nothing was missing from the code. What was missing were the environment variables, the settings a host gives a site at build and run time, and the secrets among them. This guide is how to find them, list them, and move them.

Published September 11, 2026 · About a 9 minute read

What you’ll learn

  • What environment variables are and why they’re deliberately not in the code
  • Where they live on Vercel, Netlify and Cloudflare, and in a builder
  • How to tell a public, browser-visible value from a real secret
  • How to inventory every variable and trace each one to the service that issued it
  • When a credential should be rotated rather than copied

The gap between the code and the running site

A repository holds everything about a site except the things that are specific to where it runs and the things that must stay secret. Both are supplied to the site by its host, at build time and at run time, as environment variables. Copy the code to a new host without them and the site builds, then fails the moment it tries to reach a database, sign someone in, send an email or take a payment.

This is by design, and it’s the right design. It just means that “I have the repository” is a necessary step towards portability and not the last one. This guide is the last one.

What an environment variable is

An environment variable is a named value that a program reads from its surroundings rather than from its own code. A website might read DATABASE_URL to find its database and RESEND_API_KEY to send email. The names are chosen by whoever wrote the code; the values are set wherever the code runs. The same code with different values runs against a different database, which is how one repository can serve a test site and a live one.

Two consequences follow. First, the names are in the code, so a developer or coding agent can list what the site expects. Second, the values are not in the code, so they have to be moved separately, and some of them can’t be moved at all, only reissued.

Where they live

  • On the host, in the project’s settings. Vercel has an Environment Variables page per project, with separate values for production, preview and development. Netlify has the same under the project’s environment variables. Cloudflare Workers distinguishes plain variables from secrets. This is where the live site’s values are.
  • On a developer’s computer, in a file called .env or .env.local in the project folder. Frameworks read it during local development. It is supposed to be excluded from the repository, and usually is.
  • Inside an AI builder, in a settings panel that may be called secrets, environment, integrations or connections. A builder that hosts the site holds the values the way a host does.
  • At the service that issued them. The value of an API key came from the API’s dashboard. Some services let you see it again there; many show it once and never again.

Why secrets don’t go in the repository

A repository’s history is permanent and is copied whole to every computer and host that clones it. A password committed once is in every copy forever, even after a later commit removes it from the current files. GitHub’s own guidance is that if a secret has been committed, the first step is to revoke or rotate it, not to try to scrub the history. GitHub also scans repositories for known secret formats and can block a push that contains one, which is helpful but not a substitute for keeping them out.

So the rule is: names in the code, values on the host. If you find a file in the repository called .env with real values in it, treat every value in it as exposed and rotate them.

Public values versus secrets

Not every environment variable is secret. Some are deliberately sent to the visitor’s browser, because the code that runs there needs them: a map provider’s public key, an analytics ID, a payment provider’s publishable key, the address of the site itself. Others must never leave the server: database passwords, secret API keys, the key that signs sign-in sessions.

Frameworks mark the public ones by name:

FrameworkPublic if the name starts withMeaning
Next.jsNEXT_PUBLIC_The value is built into the JavaScript sent to browsers. Anyone can read it.
Vite (used by many AI builders)VITE_Same: bundled into the client code and visible to anyone.
Anything elseNo prefixStays on the server. Treat as secret unless the issuing service says otherwise.

Services follow the same split. A payment provider issues a publishable key, safe in the browser, and a secret key, never in the browser. Supabase issues a publishable key (formerly called the anon key) for the browser and a secret key (formerly service_role) that bypasses every access rule and must stay on the server. If a secret value has ended up in a NEXT_PUBLIC_ or VITE_ variable, it is already public, and it needs rotating.

How to inventory them

  1. List the names the code expects. Ask your coding agent, or a developer: “List every environment variable this project reads, and what each one is for.” It can search the code for them in a minute. Many projects also ship a file called .env.example that lists the names with blank values.
  2. List the names the host has. Open the host’s environment variables page and write down every name, and which environments it’s set in. Vercel’s command-line tool can write the variables to a local file and Netlify’s can list them, which a developer can do for you; values marked sensitive or secret will be hidden either way.
  3. Reconcile the two. A name the code expects but the host lacks is a bug waiting to happen. A name the host has but the code no longer reads is clutter, or a clue about a feature someone removed.
  4. For each variable, record four things: the name, what it’s for, which service issued the value, and whether it’s public or secret. Don’t write the secret values themselves into this list; note where they can be read or regenerated.
  5. For each issuing service, check whose account it is. A key issued from a developer’s personal account stops working when they close it. The data guide goes through the services one by one.

The usual kinds, and who issues them

Variable looks likeWhat it isWhere the value comes from
DATABASE_URL, POSTGRES_URLThe database’s address, username and password in one string.The database provider’s dashboard. Contains the password, so it’s a secret. Rotated by resetting the database password.
*_API_KEY, *_SECRET_KEY, *_TOKENA key that lets the site act as your account at some service.That service’s API keys page. Often shown once at creation. Rotated by creating a new key and deleting the old.
AUTH_SECRET, NEXTAUTH_SECRET, SESSION_SECRETA random value the site uses to sign sign-in sessions.Generated, not issued. Rotating it signs everyone out, which is fine.
*_CLIENT_ID and *_CLIENT_SECRETCredentials for “sign in with Google” and similar.The provider’s developer console, under an account that must be yours. Callback URLs there must list your domain.
RESEND_API_KEY, SENDGRID_API_KEY, SMTP_*Credentials for sending email.The email service. Tied to a verified sending domain in that account.
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRETPayment provider credentials. The webhook secret proves that payment notifications really came from the provider.The payment provider’s dashboard. Webhook secrets are per endpoint, so a new host usually needs a new one.
NEXT_PUBLIC_*, VITE_*Public configuration for the browser.Various. Safe to copy; not safe to put secrets in.

Recreating them on another host

  1. Create the project on the new host and open its environment variables page.
  2. Enter every name from your inventory, in the production environment. Copy the values that are readable and non-sensitive.
  3. For values that can’t be read from the old host, go to the issuing service and generate a new one. Enter it on the new host, and keep the old one valid until the move is finished, so the old host keeps working as a rollback.
  4. For values tied to the host itself, such as a payment webhook secret or a sign-in callback URL, register the new host’s address with the service and use the new value it gives you.
  5. Build and test on the new host’s own address before changing DNS. A missing or wrong variable shows up here, not in front of visitors. The host move guide places this in sequence.
  6. Mark secrets as sensitive on the new host where it offers that; Vercel and Netlify both can make a value unreadable after it’s saved.

When to rotate instead of copy

Rotating means issuing a new value and retiring the old one. Do it when:

  • The value was ever committed to a repository, pasted into a chat, emailed, or stored in a shared document.
  • The value was issued from someone else’s account, and that person is leaving or the account is being closed. Reissue from an account you own.
  • A secret has been living in a public variable.
  • You can’t read the current value and don’t know who can.
  • It’s been a long time and nobody remembers who has seen it.

Rotation is routine at every service: create the new key, update the host, confirm the site works, delete the old key. Some make it one step; Stripe’s rotate action, for instance, issues the replacement and keeps the old key working for up to seven days so you can switch without a gap. The one to plan around is the database password, because every copy of the connection string changes with it. The sign-in secret is the easiest, because nothing external depends on it; changing it just signs everyone out once.

Checklist

  • I have a list of every environment variable the code reads, with what each one is for.
  • I have a list of every variable set on the host, and the two lists agree.
  • Each variable is marked public or secret, and no secret sits in a public-prefixed name.
  • For each secret, I know which service issued it and whose account that is.
  • No file with real secret values is in the repository.
  • Any value that was ever exposed, or issued from an account I don’t own, has been rotated.
  • The list lives in my password manager, not in the repository and not in a chat.

Sources

The rules and product behaviour described above were checked against these pages when the guide was last updated. If something here has changed, these are the places to look.

Keep going

Or go back to the overview of what owning your site means, which ties all of these together.

  • How to put your website’s code in GitHub

    How to get the actual source code of your website into a GitHub repository you control, whether it’s already on GitHub under someone else’s account, only on a developer’s computer, or inside an AI or site-building platform. With a verification checklist.

    About a 11 minute read

  • How to move your website to a new host without downtime

    The sequence for moving a website between hosts while it stays online: inventory the current setup, build and verify on the new host’s own address first, reproduce the configuration, lower TTLs, change only the website records, watch HTTPS, keep email working, and keep a way back.

    About a 10 minute read

Get started

Your first site is free.

Sign in with GitHub, connect the site you built with AI, and change its words yourself. Free covers one site, with no card and no end date. Pro covers up to five.

Questions first? Email hello@refino.dev. We read every message and reply ourselves.