Why .env files exist

The .env file pattern, popularised by the dotenv library, solved a real problem: keeping configuration values out of source code so developers could work with different settings across environments. Load secrets from a file, read them via process.env, and keep the file out of version control with .gitignore.

For local development, this works fine. The mistake is treating the same pattern as a production security strategy. A .env file on a production server is a plaintext file sitting on disk, readable by any process with file system access, exposed by misconfigured web servers, and persisted in Docker image layers and deployment artifacts.

Five ways .env files leak secrets

1. Committed to git

The most common leak. A developer creates a .env file, forgets to add it to .gitignore (or adds the wrong filename), and pushes it. Even if they delete it in the next commit, the secret lives permanently in git history. GitHub's own research shows that millions of secrets are pushed to public repositories every year. We routinely recover production credentials from git history during penetration tests.

2. Docker image layers

A common Dockerfile pattern: COPY .env . in an early stage, then RUN rm .env later. The deletion only creates a new layer — the original layer with the .env file intact is still part of the image. Anyone who pulls the image can extract every layer and read the file. Use docker history or dive to verify this yourself.

3. Exposed by web servers

If your .env file sits in a directory served by Nginx or Apache, and the server isn't configured to block dotfiles, it's accessible via https://yourapp.com/.env. We test for this in every web application pentest. It's a trivial check, and it succeeds more often than you'd expect.

4. Error pages and debug output

Frameworks like Django (with DEBUG=True), Laravel (with APP_DEBUG=true), and Express (with verbose error handlers) can dump environment variables in error responses. A single unhandled exception can display your database password, API keys, and JWT secrets to anyone who triggers the right error. We cover framework-specific fixes in our Django security guide and Laravel security guide.

5. Backup and deployment artifacts

Server backups, rsync copies, CI/CD artifacts, and scp transfers all pick up .env files. If your deployment process copies the project directory to a server, the .env file travels with it — often to locations with broader file permissions than intended.

Real-world exposure

.env accessible via HTTP on a payment platform

During a routine assessment of a Nigerian fintech's staging environment, we accessed the .env file directly via the web server. It contained production Paystack secret keys, database credentials, and the JWT signing secret. The staging server had been set up by copying the production configuration.

The .env.example pattern

Every project should include a .env.example file — committed to git — that documents every required environment variable with placeholder values. This serves as documentation for new developers without exposing real secrets.

The format is simple:

# .env.example — commit this file
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxx
JWT_SECRET=replace-with-a-strong-random-value
REDIS_URL=redis://localhost:6379

Pair this with a startup check that validates all required variables are present and non-empty. Fail fast in development, fail loud in production. Never silently fall back to defaults for security-critical values.

Production secrets management

In production, secrets should come from infrastructure, not files. The hierarchy of approaches, from simplest to most robust:

Platform environment variables

Vercel, Heroku, Render, Railway, and Fly.io all provide environment variable configuration through their dashboards or CLIs. These are injected into your runtime process without touching the filesystem. This is the minimum viable approach. For details on each option, see where to store API keys and secrets.

CI/CD secret injection

GitHub Actions Secrets, GitLab CI Variables, and CircleCI Contexts store encrypted values that are injected at build or deploy time. Scope secrets per environment. Never use the same credentials across staging and production.

Secret management services

AWS Secrets Manager, Google Secret Manager, HashiCorp Vault — these provide encryption at rest, access policies, audit trails, and automatic rotation. If you're processing payments or handling KYC/BVN data, a managed secret service isn't optional — it's a compliance expectation under NDPA/NDPR and PCI DSS.

Want to know if your secrets are leaking through .env files, Docker layers, or git history?

Get a secrets audit

Practical hardening checklist

Key takeaway

The .env file is for development, not production

In development, .env files are fine. In production, secrets should come from your platform's environment configuration, a CI/CD pipeline, or a dedicated secret manager. The file should never exist on a production server, in a Docker image, or in git history.

Related reading

Blog: Where to store API keys · Can hackers see your source code? · Cloud security checklist

Guides: Fintech security checklist · PCI DSS compliance · NDPA/NDPR compliance

Services: Penetration testing · Secure architecture review · Vulnerability assessment