DEV Community

Cover image for 10 Things Nobody Tells You About process.env
Odejobi Abiola Samuel
Odejobi Abiola Samuel

Posted on

10 Things Nobody Tells You About process.env

10 Things Nobody Tells You About process.env

I've burned myself on most of these so you don't have to. Here's what I wish someone had told me early on.

1. Keys are case-sensitive on Linux, case-insensitive on Windows

process.env.PORT = "3000"
console.log(process.env.port) // undefined on Linux, "3000" on Windows
Enter fullscreen mode Exit fullscreen mode

This one got me during a "works on my machine" incident. My Windows dev box ran fine. The Linux CI server crashed because a teammate typed env.port instead of env.PORT. Your CI runs Linux. Your dev box probably runs macOS or Windows. Case-sensitivity differences will bite you.

How to handle it: Use a validation layer that throws on missing keys. A simple getEnv("PORT") will catch typos at startup.

2. Values are always strings

console.log(typeof process.env.PORT) // "string" even if you set PORT=3000
Enter fullscreen mode Exit fullscreen mode

Number(process.env.PORT) can return NaN without throwing. Boolean values like "false" are truthy strings.

How to handle it: Always parse. If you use a schema library like CtroEnv, it coerces types and throws on invalid input.

3. process.env is NOT the same as .env

This confused me for way too long. process.env is whatever the shell gave the process. A .env file is just a text file dotenv reads to populate process.env. Node doesn't touch .env files on its own.

// This won't read .env automatically
console.log(process.env.MY_VAR) // undefined
Enter fullscreen mode Exit fullscreen mode

How to handle it: Call dotenv.config() at entry, or use @ctroenv/node which loads .env files automatically.

4. You can set env vars per-command

PORT=4000 node app.js
Enter fullscreen mode Exit fullscreen mode

This sets PORT only for that single process. It doesn't pollute your shell session. Super useful for one-off runs or testing different configurations without editing files.

console.log(process.env.PORT) // "4000"
Enter fullscreen mode Exit fullscreen mode

5. process.env is mutable at runtime

process.env.DATABASE_URL = "postgres://hacker:gotme@evil.com/db"
Enter fullscreen mode Exit fullscreen mode

I've seen code that modifies process.env to "fix" config at runtime. Don't do this. If something is wrong, fail fast and fix the source. Mutating process.env makes debugging a nightmare — you can't trust what you see anymore.

If you're using CtroEnv, the returned object is frozen. You literally can't mutate it.

6. Next.js inlines NEXT_PUBLIC_ vars at build time

// This gets replaced at BUILD time, not runtime
console.log(process.env.NEXT_PUBLIC_API_URL)
Enter fullscreen mode Exit fullscreen mode

Next.js replaces process.env.NEXT_PUBLIC_* references with their actual values during next build. After that, changing the env var on the server does nothing. You have to rebuild.

What this means: If you change NEXT_PUBLIC_API_URL on your production server, your app still uses the old value. Found this out the hard way during a hotfix.

7. process.env is not available in the browser

Browsers don't have process. If you're using Webpack or Vite, they emulate process.env at build time for variables you specifically expose.

// In the browser with Vite:
console.log(import.meta.env.VITE_API_URL) // works
console.log(process.env.PORT)             // ReferenceError
Enter fullscreen mode Exit fullscreen mode

How to handle it: Use Vite's import.meta.env with the VITE_ prefix, or reach for a framework adapter that handles this split automatically.

8. NODE_ENV is not set by default

I used to assume NODE_ENV was always there. It's not. Node.js doesn't set it. Your framework probably does — Express sets it to "development" by default, Next.js sets it during build — but if you're writing a bare Node script, you need to set it yourself.

if (process.env.NODE_ENV === "production") {
  // This might never run if you forgot to set it
}
Enter fullscreen mode Exit fullscreen mode

How to handle it: Always provide a default. Or validate it exists if your app can't run without it.

9. Env var values are limited to ~32KB on some systems

This one's rare but brutal when it hits. Some Unix systems cap a single environment variable value at 32KB (or even smaller on older kernels). Windows has a 32,767 character limit per variable. If you're stuffing a PEM-encoded certificate or a large JSON blob into an env var, you might hit invisible truncation.

How to handle it: Use files for large config values. Read them from disk instead of env vars.

10. Env vars are inherited by child processes

const child = spawn("node", ["worker.js"])
// child inherits ALL of parent's env vars
Enter fullscreen mode Exit fullscreen mode

This means every child process gets your secrets, your database credentials, your API keys — even if the child doesn't need them. If the child is a third-party CLI tool or something you don't fully control, those secrets are now in its memory space.

How to handle it: Be explicit. Pass only what's needed:

spawn("node", ["worker.js"], {
  env: { ONLY_WHAT_THEY_NEED: "value" }
})
Enter fullscreen mode Exit fullscreen mode

Or use CtroEnv's secret masking to at least prevent accidental logging of sensitive values.


Links: GitHub | Docs

Top comments (0)