1. Home
  2. Guides
  3. Handling Environment Variables Safely

Keeping credentials out of git, logs and shell history

Environment files are dense with credentials, hard to review because the values are visible, and easy to commit by accident. A few conventions remove most of the risk.

By Enrique M. · 6 min read · Last reviewed 2026-09-30

Commit the keys, not the values

The pattern that works everywhere is a committed `.env.example` containing every key with an empty or placeholder value, and a real `.env` that is listed in `.gitignore`. A developer copies the example, fills in the values, and the file is never staged.

The example file doubles as documentation. A new service cannot be configured correctly without knowing which variables it needs, and a list of names is exactly that.

# .env.example — committed
DATABASE_URL=
API_TOKEN=
SESSION_SECRET=
LOG_LEVEL=info

The .gitignore entry has to be early

A `.gitignore` rule only affects files that are not already tracked. If `.env` was committed at some point, adding it to `.gitignore` afterwards changes nothing, and the secrets stay in the history.

If that has already happened, the credential is compromised. Rotate it in the provider, then remove it from the history with a filter tool and force-push. Rotating first matters, because rewriting history does not un-push the old commits to anyone who already cloned the repository.

  • Check with `git ls-files | grep env` whether the file is tracked.
  • Rotate every exposed credential before touching the history.
  • Add the file to `.gitignore` before the first commit, not after.
  • Use a secret scanner in CI so the second incident is caught automatically.

A shell history is a log file

Typing `export API_TOKEN=sk_live_…` puts the secret into your shell history, and therefore into every backup of it. Reading a token from a file or from a password manager avoids that entirely.

  • `set -a; . ./.env; set +a` loads a file without echoing the values.
  • A password manager or a secret manager keeps the value out of the shell entirely.
  • Never pass a secret as a command-line argument: it is visible in the process list.
  • CI should inject secrets from the platform, not from a file in the repository.

Debugging a variable that is not set

A duplicate key in a .env file is a silent override, because most parsers apply the value defined last. A value with a stray quote character is a second common cause, as is a value containing a space that nobody quoted.

A quick check of the parsed key list, with the values masked, shows all three immediately. That is why masking by default is more useful than showing the raw file: you can see the structure without exposing the credentials.

What to take away

  • Commit a `.env.example` with the keys; keep the real file out of git.
  • Adding a file to `.gitignore` after it was committed does nothing — rotate the credential.
  • Secrets on a command line are visible in the process list.
  • A duplicate key silently overrides the earlier value.

Related tools