.env Security Best Practices
Almost every .env leak follows the same story: a file that was never supposed to be committed gets committed anyway. Here's how to prevent it, detect it, and clean up after it.
Generate a Safe .env.example Right Here
Paste your real .env file below and generate a stripped .env.example — every key preserved, every value emptied or replaced with a placeholder. This is the file you commit; the original never should be.
Privacy note: generation happens entirely in your browser. Your real values are never sent anywhere, including to us.
Rule One: Never Commit Secrets
Every other practice on this page is a backstop for the moment rule one fails, because it eventually does — a rushed commit, a new team member who doesn't know the convention, a copy-pasted git add . before checking git status. The first line of defense is making the mistake structurally hard to make:
# .gitignore .env .env.local .env.*.local .env.development .env.staging .env.production !.env.example !.env.template
Notice the pattern: ignore every real environment file by name or pattern, then explicitly un-ignore (!) the example/template files youdo want committed. Blanket-ignoring .env* and then un-ignoring the example is more robust than trying to enumerate every real filename, since it also covers variants a teammate invents later, like .env.jane-local.
Add this to .gitignore before the first .env file is created in a new project, not after. A pattern added retroactively doesn't retroactively remove files already tracked by git — you need git rm --cached .env as well, and by then it may already be in history (more on that below).
Secret Scanning: Catching What Slips Through
.gitignore only stops what you never git add. Secret scanning catches the file that got added anyway, ideally before it's ever pushed:
- GitHub push protection scans pushes for patterns matching known secret formats (cloud provider keys, API tokens with recognizable prefixes) and blocks the push before the secret ever reaches GitHub's servers, rather than just alerting after the fact. It's enabled by default for public repositories and can be turned on for private ones.
- GitHub secret scanning (the detection half) continuously re-scans repository history and open pull requests, and for supported providers can automatically notify the provider, who may revoke the key themselves within minutes.
- gitleaks is an open-source scanner you run yourself — locally, in CI, or as a pre-commit hook — using regex and entropy detection to flag likely secrets in a diff or an entire git history. Because it runs before code leaves your machine, it's the earliest possible catch point, earlier than anything server-side.
Layer them: gitleaks as a pre-commit hook catches mistakes before they're even committed locally; GitHub push protection catches what escapes that; periodic secret-scanning re-scans catch anything already sitting in history from before either was enabled.
What to Do After a Leak
If a real secret was committed — even briefly, even in a private repo, even if you deleted it in the very next commit — treat it as compromised. Git history keeps every version of every file by design, and a secret that touched a commit is retrievable by anyone who can clone the repo or has ever fetched it, indefinitely.
- Rotate the secret first, always. Generate a new API key, database password, or signing secret and deploy it. This is the only step that actually neutralizes the exposure; everything else is cleanup.
- Revoke the old one. A rotated-but-not-revoked secret is still valid and still exploitable until it expires or is explicitly killed.
- Check for unauthorized use. Review provider audit logs (API call logs, database connection logs, cloud provider CloudTrail-style history) for activity between when the secret leaked and when you revoked it.
- Then, and only then, consider history rewriting. Tools like
git filter-repoor BFG Repo-Cleaner can strip a file or string from every commit in history. This is disruptive — it rewrites every commit hash after the point of change, invalidates every existing clone and open pull request, and requires a coordinated force-push everyone must re-clone against. Do it for hygiene after the secret is already dead, not as a substitute for rotation. A secret that's been rotated is safe to leave in history; one that hasn't is not made safe by removing it from history, because forks, local clones, and CI caches may already have it.
.env.example: The Contract You Actually Commit
.env.example (sometimes .env.template or .env.sample) is the committed, secret-free sibling of your real .env — every key a new developer or deploy target needs to set, with empty or placeholder values instead of real ones. It serves two purposes at once: documentation (a new teammate runs cp .env.example .env and knows exactly what to fill in) and a drift detector (if a key exists in your real .env but not in the example, or vice versa, someone forgot to update one of them).
Keep it honest by regenerating it from the real file rather than hand-editing it separately — hand-maintained examples drift out of sync within a few sprints as keys get added and removed from the real .env. The generator above does exactly this: point it at your current .env and it produces an up-to-date example in one step, preserving comments and structure so the example still reads like documentation, not just a bare key list.
For the full syntax rules that govern what a valid .env file (and by extension, a valid .env.example) can contain, see the complete .env file format guide. For how to keep .env.example synchronized across dev, staging, and production without drift, see managing .env files across environments.
Further Reading
- OWASP Secrets Management Cheat Sheet
Comprehensive guidance on secret storage, rotation, and handling across the software lifecycle.
- GitHub push protection documentation
How GitHub blocks pushes containing recognizable secret patterns before they reach the server.
- gitleaks
Open-source secret scanner for git diffs, commit history, and pre-commit hooks.
- git filter-repo documentation
The recommended modern tool for rewriting git history, including removing files that were committed by mistake.