The most common way secrets leak is not a hack — it is a git push. Here are the rules I follow, which have kept credentials out of my repositories.

Rule 1: secrets live in environment files, never in code

The app reads its configuration from an environment file that is not in the repo:

# /opt/myapp/.env  (root-owned, chmod 600)
DB_PASSWORD=...
JWT_SECRET=...
API_KEY=...

systemd loads it via EnvironmentFile, and the app never hardcodes a secret. The repo contains a .env.example with placeholder names only.

Rule 2: .gitignore the real files

.env
.env.local
configs/config.yaml   # any local config that may hold secrets
/data/
/logs/

The example file is committed; the real one is not. If a secret must live in a config file, ignore that file and commit only the example.

Rule 3: commit early and check before you push

Once a secret is in git history, removing it later is painful — it lives in every past commit. Before pushing, scan:

git diff --cached --name-only   # what am I about to commit?

And run a quick secret scan (gitleaks, trufflehog, or even a grep for -----BEGIN and sk-/AKIA patterns) as part of CI if the project is shared.

Rule 4: when one leaks anyway

Assume it is compromised and rotate it immediately — new key, revoke the old, update the environment file, and scrub what you can from history with history-rewriting (and coordinate if others have cloned it). Time-to-rotate matters more than time-to-rewrite.

Secrets are a boring, high-stakes discipline. The environment-file pattern plus a .gitignore is 90% of it. If you are running services on a VPS that holds these secrets, the calculator below shows what that VPS really costs you.

VPS / self-hosted$6.00
Managed cloud$18.65
Difference per month$12.65
NoteSample pricing — verify against current provider prices