Environment variables done right: .env best practices for 2026

Environment variables are the standard way to keep secrets out of your codebase, but “just use a `.env` file” is where most tutorials stop. In practice, sloppy `.env` handling is one of the most common ways credentials leak in 2026 — through git history, logs, or client-side bundles. Here’s how to do it properly.

The basics, done right

  • One `.env` file per environment: `.env.development`, `.env.staging`, `.env.production` — never share production secrets into a dev file.
  • `.env` is always in `.gitignore`, with no exceptions, ever.
  • Commit a `.env.example` with every variable name and a placeholder or dummy value, so new developers know what’s required.
  • Never log `process.env` or any object that might contain it, even for debugging.

A clean `.env.example`

# .env.example — copy to .env and fill in real values
NODE_ENV=development
PORT=3000

DATABASE_URL=postgres://user:password@localhost:5432/app_dev
REDIS_URL=redis://localhost:6379

JWT_SECRET=replace_with_a_long_random_string
STRIPE_SECRET_KEY=sk_test_replace_me

# Never prefix server-only secrets with a public-exposed prefix
# like NEXT_PUBLIC_ or VITE_ — those get bundled into client JS

Generating strong secrets

Never hand-type a “secret” like `mysecret123`. Generate real entropy:

# 32 random bytes as hex (good for JWT secrets, session keys)
openssl rand -hex 32

# Or with Node.js, no dependencies needed
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

# Or a URL-safe base64 secret
openssl rand -base64 32

Client vs server: the mistake that actually leaks data

Frameworks like Vite, Next.js, and Create React App expose any env variable prefixed with `VITE_`, `NEXT_PUBLIC_`, or `REACT_APP_` directly in the browser bundle. That’s fine for a public API base URL, but it is never safe for a secret key, database password, or private token — those must stay unprefixed and server-only.

# .env — safe: server-only, never bundled
DATABASE_URL=postgres://...
STRIPE_SECRET_KEY=sk_live_...

# .env — DANGEROUS if you actually need this secret,
# because VITE_ / NEXT_PUBLIC_ vars ship to every visitor's browser
VITE_STRIPE_SECRET_KEY=sk_live_...   # never do this

Loading env vars safely in code

// Node.js with dotenv, validated at startup instead of trusting blindly
import 'dotenv/config';

function requireEnv(name) {
  const value = process.env[name];
  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }
  return value;
}

const databaseUrl = requireEnv('DATABASE_URL');
const jwtSecret = requireEnv('JWT_SECRET');

Production checklist

  1. Secrets in production live in your host’s secret manager (Vercel/Netlify env vars, AWS Secrets Manager, Doppler, etc.), not a plain `.env` file sitting on disk.
  2. Rotate secrets on a schedule and immediately after any team member offboards.
  3. Different secrets per environment — a leaked staging key should never unlock production.
  4. Restrict file permissions on any `.env` file that does live on a server: `chmod 600 .env`.

Pairing solid `.env` hygiene with a proper `.gitignore` closes most of the common leak paths — see git add/commit/push in one command for a workflow that still lets you double-check staged files before every push.

Quick FAQ

Is it safe to store secrets in CI/CD environment variables?

Yes, as long as they’re set in the CI platform’s secret store (not committed in a YAML file) and masked in logs, which most modern CI providers do by default.

Should `.env.example` ever contain a real value?

No. Use placeholders like `replace_me` or clearly fake test values, never a working credential.

What’s the safest way to share a `.env` file with a teammate?

Use a secrets manager or an encrypted channel, never Slack, email, or a shared doc — those are all commonly breached or logged.

Leave a Reply

Your email address will not be published. Required fields are marked *