guide5 min readupdated 2026-04-17

How to hide .env from git (and recover if you already committed it)

Step-by-step: keep .env out of git from day one, and what to do when a secret leaks into your commit history. Includes rotation checklist.

TL;DR

Add .env* to .gitignore before your first commit. If you already pushed a .env, rotate every secret in it immediately, then scrub git history. Committing a .env.example is fine and encouraged.

Step 1 — Set up .gitignore correctly

Add these lines to the .gitignore at your project root:

# Env files
.env
.env.local
.env.*.local
.env.development
.env.production
.env.test

# Keep the example — it's safe
!.env.example

The ! prefix is an explicit allow — it ensures .env.example still gets committed even though .env* is ignored.

Modern frameworks ship with this pattern pre-configured. Next.js, Create React App, Nuxt, and Rails all add .env*.local to .gitignore on create. Double-check anyway — one missing line can cost you an afternoon of rotation work.

Step 2 — Untrack an already-tracked .env

If you accidentally added .env to git (even before ignoring it), it's still tracked. Remove it from the index but keep the local file:

git rm --cached .env
git commit -m "stop tracking .env"
git push

This stops future changes from being committed, but the file is still in your git history. Anyone who clones the repo can check it out from any previous commit. If the .env had real secrets, see Step 4.

Step 3 — Commit a .env.example instead

Take your .env, strip the values, keep the keys and comments, and commit the result as .env.example. You can do this in the browser — paste your .env, download the example, commit it.

# .env.example
DATABASE_URL=
STRIPE_SECRET_KEY=
JWT_SECRET=
NEXT_PUBLIC_SITE_URL=

New hires clone the repo, copy .env.example .env.local, fill in values. Your structure is documented, your secrets are not.

Step 4 — You already pushed secrets. Do this now.

4a. Rotate every secret immediately

Git history is effectively permanent once pushed. Assume the secret is compromised. For each credential in the leaked file:

  • AWS keys → IAM → delete, create new
  • Stripe keys → Dashboard → Developers → API keys → roll
  • GitHub tokens → Settings → Personal access tokens → revoke + regenerate
  • OpenAI / Anthropic → dashboard → revoke + create
  • Database passwords → change the DB user password
  • JWT secrets → generate a new one → redeploy (every user will be logged out)

Scan the file first with the leak checker so you don't miss anything. It flags 15+ known secret patterns in seconds.

4b. Remove the file from git history

Use git filter-repo (recommended) or the older BFG:

# One-time install
brew install git-filter-repo

# Remove the file from every commit in history
git filter-repo --path .env --invert-paths

# Force-push (destructive — inform your team first)
git push origin --force --all
git push origin --force --tags

Warning: force-push rewrites history. Anyone who cloned the repo before this point needs to re-clone, or their copy will conflict.

4c. Audit access logs

For each rotated secret, check usage logs for the period between the leak and the rotation. If you see requests from unfamiliar IPs or at unusual times, treat it as a real incident — check for data exfiltration, new resources created, billing anomalies.

Step 5 — Automate detection

Two tools worth adding:

  • gitleaks — pre-commit hook + CI scanner. Blocks commits that contain known secret patterns.
  • trufflehog — scans entire git history. Use in CI to catch older leaks.

GitHub also has native secret scanning — it automatically alerts you (and sometimes the upstream provider) when known secret patterns land in a public repo.

Checklist

  • [ ] .env* in .gitignore
  • [ ] .env.example committed
  • [ ] git ls-files | grep .env returns only the example
  • [ ] pre-commit hook (gitleaks / trufflehog) installed
  • [ ] host dashboard (Vercel, Railway) has all required keys set
  • [ ] If a secret ever leaked: rotated, history scrubbed, access logs reviewed

FAQ

I already committed my .env — is deleting it enough?

No. Git retains the file in history. Anyone who clones the repo or checks older commits can still read it. Rotate the leaked secrets immediately, then remove the file from history with git filter-repo or BFG.

Does .gitignore work retroactively?

No. .gitignore only prevents future commits. Files already tracked must be untracked explicitly (git rm --cached .env) and the history must be rewritten if you want to remove them from past commits.

Can I commit .env.example?

Yes — and you should. It shows structure without secrets. Generate one from your live .env with the example generator.

Related tools

Continue reading