Cloud Architecture·8 min read·

Secrets, Env, and Least Privilege Across Nodes

Give each VPS only the secrets it needs: split EnvironmentFiles, systemd credentials, and per-role database users so a stolen node is not a stolen fleet.

NB

Netbay Developer Relations

Netbay Engineering

On this page

A stolen web node should not contain the replica's replication password, the backup rsync key, and the payment provider's live credential. Least privilege across services is an architecture of who is allowed to know what, not a lecture about not committing .env to git. You already know not to put secrets in the repository. The operational problem is that every process on every VPS still reads the same file because copying one bundle was easier at 11pm. Split the bundle.

One Role, One Secret Set

Draw the fleet as roles: edge, api, worker, postgres, redis, backup. Each role is one or more VPS nodes. Each role gets an EnvironmentFile that only that role can read. The API needs the database URL for a least-privilege user that can INSERT orders and SELECT products. It does not need the replicator password. The worker needs the GST portal token. The API does not. The backup node needs SSH to the database node and encryption keys for dumps. Nothing else should be able to read those keys.

ini
# /etc/app/api.env  mode 0640, owner root:api
DATABASE_URL=postgres://app_api:REDACTED@10.0.0.20:5432/app
REDIS_URL=redis://10.0.0.12:6379/0
FLAG_DSN=postgres://app_flags:REDACTED@10.0.0.20:5432/app

# /etc/app/worker.env  mode 0640, owner root:worker
DATABASE_URL=postgres://app_worker:REDACTED@10.0.0.20:5432/app
REDIS_URL=redis://10.0.0.12:6379/0
GST_API_KEY=REDACTED
SMTP_URL=smtps://user:REDACTED@mail.example.com:465

systemd can load those files without exporting them into a shell that users can inspect. Pair with a dedicated unix user per unit. The api user cannot read worker.env if the group and mode are correct. That is the whole control.

ini
# /etc/systemd/system/app-api.service
[Service]
User=api
Group=api
EnvironmentFile=/etc/app/api.env
WorkingDirectory=/srv/app
ExecStart=/usr/bin/node dist/server.js
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/var/lib/app /tmp

ProtectSystem and ReadWritePaths shrink what a compromised process can rewrite. They do not replace split secrets. If the API process can still open /etc/app/worker.env, you did not split anything.

Database Users Are Walls

Postgres roles are cheaper than a second cluster. Create app_api, app_worker, app_migrator, replicator. GRANT only what each needs. Migrator is used by a one-shot unit you start by hand. It is not in the API env. A SQL injection in a web handler then cannot DROP TABLE if the role cannot. That is architecture, not a WAF.

sql
CREATE ROLE app_api LOGIN PASSWORD 'x';
CREATE ROLE app_worker LOGIN PASSWORD 'y';
CREATE ROLE app_migrator LOGIN PASSWORD 'z';

GRANT CONNECT ON DATABASE app TO app_api, app_worker, app_migrator;
GRANT USAGE ON SCHEMA public TO app_api, app_worker, app_migrator;

GRANT SELECT, INSERT, UPDATE ON orders, customers TO app_api;
GRANT SELECT ON products TO app_api;
GRANT SELECT, INSERT, UPDATE ON outbox TO app_api;

GRANT SELECT, UPDATE ON outbox TO app_worker;
GRANT SELECT ON orders TO app_worker;
GRANT INSERT ON processed_events TO app_worker;

Rotate by creating a new password, reloading the unit, then ALTER ROLE to drop the old secret. Keep the previous env file on the backup node, not on the web node. High-Speed SSD snapshots of a web VPS will contain whatever was on disk; do not leave leftover env copies in /root.

Movement and Blast Radius

Secrets cross the network at provision time. Use SSH from the control plane, write the file, chmod, and do not leave the payload in shell history. Do not curl secrets from a public bucket. Netbay does not offer object storage as a product, and you should not improvise one as a world-readable nginx alias. If you must fetch a file, fetch over SSH or an internal HTTPS endpoint that checks a per-node client certificate.

A node that is being retired is a secret that is still live until you rotate. Snapshot, wipe, or rebuild is not rotation. Change the database password, the Redis AUTH, and the third-party tokens that lived on that box. Put that in the deprovision runbook next to the ufw cleanup.

Each role reads a different secret set API node app_api role Worker node GST + SMTP Postgres roles, not one user Backup node rsync + dump keys Control plane writes env over SSH, never stores all on one guest

What Goes in Env, What Does Not

Env is for small secrets and connection strings. TLS private keys are files with 0600, loaded by nginx, not stuffed into an environment variable that every child can see in /proc. JWT signing keys are files. Large JSON blobs of customer credentials are a table with encryption at the application layer, not a 200-line env file. Do not log EnvironmentFile values. journald will happily persist a misconfigured unit's dump if you echo the environment in an ExecStartPre.

Intel Xeon Platinum and L3/L4 DDoS do not encrypt your env files. Disk encryption of the volume is a separate decision. At minimum, secrets on disk are root-owned and not world-readable. At maximum, you use systemd credentials that unpack into a tmpfs the unit can read and the rest of the node cannot list.

Takeaway

Least privilege is a map from role to secret set. Split EnvironmentFiles, split database users, split SSH keys, and rotate on deprovision. A compromised API node should be an API incident, not a fleet-wide credential dump.

Provision separate Netbay VPS nodes in Lucknow (DC01) for api, worker, and database so the files never share a filesystem to begin with — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance