Why You Should Never Commit .env Files to Git
I've watched a .env file slip into a commit more than once, usually because .gitignore got added a commit or two late. Once that file lands in the history, deleting it from your working directory does nothing to undo the exposure.
A Password in Plain Text, Searchable Forever
Picture a rushed hotfix at the end of the day. git add ., a quick commit message, push. The .gitignore for this project never got written, so the diff looked like this:
Security researchers reported over 12 million leaked secrets on GitHub in 2023, a large share of them database credentials and API keys sitting in committed .env files. GitHub's own secret scanning flags patterns like this within minutes of a push, and bots scraping public repos are often faster than that.
What happens next depends on what leaked. A live Stripe key gets used for fraudulent charges before you notice. A database URL turns into a full data dump on a paste site. If the exposed data belongs to your users, you're also looking at breach notification obligations under GDPR or similar laws, and those come with real fines, not warnings.
How It Actually Happens
The scenario above almost never starts with someone deliberately committing a secret. It starts with a .gitignore that doesn't exist yet, on day one of a project, before anyone has thought about it. The first few commits go in clean by luck, then someone adds a .env file to hold a local database URL and it goes in with everything else.
Adding .env to .gitignore later doesn't retroactively fix anything either. Git keeps every version of a tracked file in its history, so if .env was committed once, it stays recoverable from that history even after you stop tracking it going forward. Removing it for good requires rewriting history.
Some IDEs make this worse with auto-stage or auto-commit features that pick up every changed file in the working directory. Without a correct .gitignore in place first, that includes whatever secrets happen to be sitting there.
And a common one: copying .env.example to .env, filling in real credentials for local development, then forgetting that the copy needs its own ignore rule because .env.example being safe to commit says nothing about .env.
Keeping Secrets Out of the Repo
.gitignore Before the First Commit
Write .gitignore before writing any code, not after. Cover every .env variant your project might grow into, even the ones you don't need yet:
A Pre-commit Hook as a Backstop
.gitignore only helps if it existed before the file was staged. A pre-commit hook catches the case where it didn't. Tools like git-secrets, detect-secrets, or gitleaks scan staged changes and block the commit if they match known secret patterns:
Ship .env.example Instead
Commit a .env.example with placeholder values so teammates know what variables the project needs, without any of them being real:
Audit the Repo Once in a While
Even with good habits, run a scan against your history occasionally with truffleHog or GitHub's secret scanning. Old commits from before you cared about any of this are still there:
What to Do If You've Already Committed Secrets
If you find secrets already sitting in your repository, the order you handle this in matters more than how fast you do it.
- Rotate the exposed credentials first.Do this before touching Git history. A private repository doesn't change the calculus here. Treat every exposed secret as compromised from the moment it was committed.
- Remove the secrets from Git history using git-filter-repo or BFG Repo-Cleaner:bfg --delete-files .envgit reflog expire --expire=now --allgit gc --prune=now --aggressive
- Force push to all branches, after coordinating with your team since it rewrites shared history:git push --force --all
- Notify affected parties if the exposed credentials protected user data.
Skip .env Files in Production Entirely
The safest .env file in production is the one that doesn't exist. Every major host gives you somewhere to store secrets that isn't a plaintext file on disk:
- Vercel - Environment Variables in Project Settings
- AWS - Secrets Manager or Parameter Store
- Google Cloud - Secret Manager
- Azure - Key Vault
- Docker/Kubernetes - Docker Secrets or K8s Secrets
- HashiCorp Vault - for teams that need fine-grained access policies across many services
Encryption at rest, access controls, audit logs, rotation without a redeploy: a plain .env file offers none of it. The setup cost for a real secrets manager is an afternoon. Cleaning up after a leaked production database usually costs a lot more than that, in time and in trust.