Skip to content

systemd and PM2 with encrypted environment variables

Put npx getmyenv run in ExecStart, or start PM2 through it. Covers the server token, machine approval, restarts and what PM2 writes to disk.

Oct 10, 2026 · 5 min read

A server token and an approved machine lead to one terminal command, which systemd or PM2 runs to start the app.
systemd or PM2 starts the app through getmyenv run, with a server token and an approved machine.

Before you start

  • Node 24 or newer and npx on the server.
  • A server token for Production, made in Project settings, Tokens, with the server's IP on its allowlist.
  • run fetches the values from getmyenv each time it starts. If it can't reach the server, the command does not start. A running process keeps its values.
  • Your app never sees GETMYENV_TOKEN. run drops every GETMYENV_* name before it starts the command.

1. systemd

systemd shows Environment= lines to unprivileged users, so put the token in a file only root can read and load it with EnvironmentFile=.

bash
sudo mkdir -p /etc/myapp sudo install -m 600 /dev/null /etc/myapp/getmyenv.env echo 'GETMYENV_TOKEN=...' | sudo tee /etc/myapp/getmyenv.env > /dev/null
# /etc/systemd/system/myapp.service
[Unit]
After=network-online.target
Wants=network-online.target

[Service]
User=myapp
WorkingDirectory=/srv/myapp
EnvironmentFile=/etc/myapp/getmyenv.env
Environment=GETMYENV_MACHINE_LABEL=web-1
ExecStart=/usr/bin/npx getmyenv run production -- node server.js
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
bash
sudo systemctl daemon-reload sudo systemctl enable --now myapp journalctl -u myapp -f

Use the full path that command -v npx prints. systemd's PATH is short, so a Node installed with a version manager is not on it. run passes systemctl stop on to your app. After a value changes, sudo systemctl restart myapp.

2. Approve the machine once

With Ask me, the first start creates a request, and the journal shows the Requests link. A machine's request waits up to 5 minutes for approval. If it expires, run exits and Restart=on-failure tries again.

Approve with Trust. A plain approval is a one-time grant for 1 hour, used by one read, so the next restart asks again. The machine key lives in ~/.config/getmyenv of the service user. Keep the same User=, or set GETMYENV_CONFIG_DIR, so the machine stays trusted.

3. PM2

PM2 copies the environment of the pm2 command into the app when it starts it. So start PM2 through run:

bash
export GETMYENV_TOKEN=... # server token for Production npx getmyenv run production -- pm2 start ecosystem.config.js npx getmyenv run production -- pm2 restart app --update-env
  • The app keeps the values from its start. A plain pm2 restart app reuses them. Restart through run, with --update-env.
  • Names in the ecosystem file's env: win over the environment. Leave getmyenv names out of it.
  • If the PM2 daemon is not running yet, this command starts it, and the daemon keeps the values in its own environment too.

4. What PM2 writes to disk

pm2 save writes each app's environment, values included, to dump.pm2 in PM2's home folder (default ~/.pm2). pm2 resurrect and the boot script from pm2 startup restore from that file, not from getmyenv.

To keep values off disk, skip pm2 save. Let systemd start PM2 in the foreground instead, with ExecStart=/usr/bin/npx getmyenv run production -- pm2-runtime ecosystem.config.js.

Revoking stops new reads. A running process keeps the values it started with until it restarts. More: /docs/machine-access, /docs/runtime-injection.

Related docs: Machine access, How run works, Languages