What is a .env file, and how do you keep secrets out of git?

A .env file keeps settings and secrets out of your code. It only protects you if it never reaches git, and the syntax has a few traps.

What it looks like

# Database
DATABASE_URL=postgres://app:s3cret@localhost:5432/app
PORT=3000
DEBUG=false
GREETING="Hello, world"   # quotes keep the comma and space
PRIVATE_KEY="-----BEGIN KEY-----\nMIIB...\n-----END KEY-----"

Your code then reads process.env.PORT, os.environ["PORT"] and so on. The point is that the same code runs on your laptop, a test server and production, each with its own values, and no password sits in the source.

Syntax rules that trip people up

SituationWriteWhy
Spaces around =PORT=3000Some loaders and shells treat PORT = 3000 as an error or keep the spaces
Value with spaces or #TITLE="My app #1"Unquoted, # may start a comment
Value with $PASS='pa$$word'Single quotes stop $ being expanded as a variable in most loaders
Multi-line valueKEY="line1\nline2"Escaped newlines in double quotes work almost everywhere; real line breaks only in some loaders
Numbers and booleansDEBUG=falseEverything arrives as text: "false" is truthy in JavaScript, so compare strings
export prefixexport PORT=3000Allowed by dotenv and python-dotenv, handy for source .env in a shell

There is no single official standard, so the exact rules depend on the loader. Keep values simple and quote anything unusual.

Keeping it out of git

# .gitignore
.env
.env.*
!.env.example

Then commit a .env.example listing every variable with a blank or fake value, so the next person knows what to fill in. Add the ignore line before the first commit: once a file is tracked, .gitignore no longer affects it. To stop tracking one that slipped in, run git rm --cached .env and commit, then treat everything in it as leaked.

Loading it

If you already pushed a secret

  1. Rotate it first. Create a new key or password and revoke the old one. Bots scan public repositories for keys within minutes, so assume it has been seen.
  2. Remove the file from the repo and add it to .gitignore.
  3. Optionally rewrite history with git filter-repo or BFG, and force-push. That tidies up, but forks, clones and caches may still hold the old commit, which is why step 1 comes first.

Note that Base64 is not encryption: a Base64-encoded secret in a file is still a plain secret.

Making good secrets

For session secrets, API tokens and database passwords, use long random values rather than words: 32 or more random characters, or 32 random bytes as hex. The password generator makes them in your browser with the system's secure random generator, and nothing is sent anywhere. Want to share one with a teammate without leaving it in a chat log? A one-time link from Fusenote burns itself after reading.

Secrets in the browser are a different problem: localStorage vs cookies.

Questions

Should I commit my .env file?

No. Commit a .env.example with the variable names and dummy values, and keep the real .env in .gitignore.

Does .gitignore remove a .env file I already committed?

No. .gitignore only affects untracked files. Run git rm --cached .env, commit, and rotate any secrets that were in it, because they remain in the history.

Do I need quotes in a .env file?

Only when the value contains spaces, #, quotes, $ or newlines. Double quotes allow escapes like \n; single quotes keep the value exactly as written in most loaders.

Are environment variables secure?

They keep secrets out of source code, which is the main goal. They can still leak through error pages, logs, child processes or front-end bundles, so never print them and never expose them with public prefixes like VITE_ or NEXT_PUBLIC_.

Published .

Try it

Related guides

Newest guides