VPS DeploymentBeginner 11 min Lesson 17 of 30

Environment Variables and Secrets

Get configuration and credentials to your application safely: env files, systemd, permissions and rotation.

VPS Deployment · Lesson 17 of 30
0/30 done(0%)

What is it? #

Your application needs configuration that differs per environment and credentials that must not be in source control.

On a VPS the standard mechanism is an environment file on the server, loaded by systemd and passed to the process.

The file needs the right ownership and 600 permissions, and it must never be committed. A committed credential is a credential that has leaked.

Rotation is the part most setups skip. Credentials should be changeable without redeploying code, and must be changed when someone leaves or a key is exposed.

Think of it like this #

Keys kept in a safe in the building rather than printed in the operating manual.

The manual is copied and shared. The safe is opened by exactly the people who need what is inside.

Simple example #

An application needs a database URL, a secret key and an API token. They live in an env file owned by the application user, loaded by systemd, with an example file committed to the repository.

Code #

BASH
# /srv/app/.env — owned by the service user, never committed
DATABASE_URL=postgresql://appuser:LONG-RANDOM@localhost/shop
SECRET_KEY=a-long-random-value-generated-once
STRIPE_API_KEY=sk_live_...
LOG_LEVEL=info
PAGE_SIZE=20

sudo chown appuser:appuser /srv/app/.env
sudo chmod 600 /srv/app/.env          # only the service user can read it
INI
# systemd loads it and passes it to the process
[Service]
User=appuser
WorkingDirectory=/srv/app
EnvironmentFile=/srv/app/.env
Environment=PYTHONUNBUFFERED=1
ExecStart=/srv/app/.venv/bin/gunicorn app.main:app --bind 127.0.0.1:8000

# Applying a change to the env file:
#   sudo systemctl restart myapp     (a reload does not re-read it)
TEXT
# .env.example — this one IS committed
DATABASE_URL=postgresql://user:password@localhost/dbname
SECRET_KEY=generate-with-openssl-rand-hex-32
STRIPE_API_KEY=sk_test_xxx
LOG_LEVEL=info
PAGE_SIZE=20

# .gitignore
.env
.env.local
*.pem
*.key
BASH
# Generating strong secrets
openssl rand -hex 32                  # a 64-character hex string
openssl rand -base64 32
python3 -c "import secrets; print(secrets.token_urlsafe(48))"

# Verify the running process has what you expect (as root)
sudo tr '\0' '\n' < /proc/$(pgrep -f gunicorn | head -1)/environ | grep -v KEY
# Note: this also shows that root can read any process's environment.
TEXT
Rotation, when it matters

on staff departure     rotate anything that person could read
on exposure            rotate immediately; deleting the commit is not enough
periodically           at least annually for long-lived API keys

The process: add the new credential alongside the old, deploy, verify,
then revoke the old one. Rotating in one step causes an outage.

How it works #

EnvironmentFile reads simple KEY=value lines and passes them to the process. It does not run a shell, so there is no variable expansion and no quoting subtleties — values are taken literally.

Permissions of 600 with the service user as owner mean no other account on the machine can read the credentials. This matters on any server with more than one user.

A restart is required after changing the file. systemctl reload signals the application to re-read its own configuration, but the environment is set when the process starts, so it does not pick up changes.

Committing .env.example documents every variable the application needs without exposing any value. It is what lets a new developer or a rebuilt server know what to set.

Generating secrets with openssl rand produces genuinely random values. Hand-typed secrets are shorter and more predictable than people assume.

The /proc command demonstrates an important limitation: environment variables are readable by root and visible in the process environment. They are a reasonable mechanism, not a strong secret store.

Rotating in two steps — add, deploy, verify, revoke — avoids the window where the old credential is gone and the new one is not yet in use.

Real-world use #

Env files are the default on a VPS because they are simple and work with every runtime. They are adequate for most systems.

Secret managers — Vault, AWS Secrets Manager, and similar — add rotation, audit logging and short-lived credentials. They are worth adopting when the number of secrets or the compliance requirements grow.

Committed secrets are one of the most common real incidents. Scanners watch public repositories continuously, and a leaked cloud key is typically abused within minutes. Removing it from the latest commit does nothing; it must be rotated.

Secrets also leak through logs. Logging a full request payload or an exception with configuration attached puts credentials into a system that many people can read and that is retained for months.

Backups contain secrets too, which is why the backups lesson insists on encrypting them before they leave the machine.

Common mistakes #

  • Committing a .env file with real values.
  • Leaving the env file world-readable instead of 600.
  • Reloading instead of restarting after changing the environment.
  • Logging configuration or full payloads, leaking credentials into logs.
  • Rotating in one step and causing an outage.

Practice #

Create an env file with a generated secret, set the correct ownership and permissions, and load it through a systemd unit. Commit a matching .env.example and confirm .env is ignored by git. Then change a value and verify a restart is needed for it to take effect.

Quick quiz

  1. 1. What permissions should an env file with secrets have?

  2. 2. Why does reloading not pick up env file changes?

  3. 3. What should be committed to the repository?

  4. 4. What must you do after accidentally committing a secret?

  5. 5. Why rotate in two steps?

Summary

  • Keep configuration and secrets in an env file owned by the service user at 600.
  • Load it with systemd EnvironmentFile and restart to apply changes.
  • Commit .env.example, never .env.
  • Generate secrets randomly and keep them out of logs and backups.
  • Rotate by adding the new credential first, then revoking the old.