PM2 Environment Variables: Setup, Update & Best Practices
Master PM2 environment variables: learn to set, update, and secure them using ecosystem files, --update-env, filter_env, and more. Avoid common pitfalls.

PM2 environment variables are one of those things that seem simple until they bite you. You set a variable, restart the process, and nothing changes. Or you use the wrong flag and wonder why your app is running on the wrong port. I’ve seen these mistakes trip up experienced developers, and the old PM2 documentation doesn’t help much.
This guide covers the correct way to set, update, and secure environment variables in PM2 (currently at version 7.0.3, requiring Node.js 18+). If you’re looking for a broader overview of process management, check managing applications with PM2 first. For operators running multiple apps on a VPS, the best self-hosted server management panels can help with visibility across your stack.
Common Misconception
The --env flag in PM2 does not set individual environment variables. It selects named environment blocks from an ecosystem file (e.g., --env production). Many articles, including older versions of this one, show incorrect syntax like pm2 start app.js --env PORT=3000. That doesn’t work.
Setting environment variables via the command line in PM2
The correct way to pass environment variables when starting a PM2 process uses standard Unix environment variable syntax: prefix the command with VAR=value.
# ❌ This is NOT how PM2 env vars work
pm2 start app.js --env PORT=3000
pm2 start app.js --env PORT=3000,DB_URL=mongodb://localhost:27017/mydbThe --env flag selects environment blocks from an ecosystem file, not individual variables. Passing --env PORT=3000 will either be ignored or cause an error.
# Single variable:
PORT=3000 pm2 start app.js
# Multiple variables (space-separated):
PORT=3000 DB_URL=mongodb://localhost:27017/mydb SECRET=abc123 pm2 start app.jsThis is standard Unix. The variables are set in the environment before PM2 spawns your application.
Verify it worked:
# Find your process ID first
pm2 list
# Then inspect the environment (replace 0 with your process id)
pm2 env 0
You should see PORT=3000 in the output. If it’s missing, double-check the prepend syntax. The variables must come before pm2 start.
For PM2 cluster mode with multiple instances, you’ll typically want different ports per instance. increment_var handles that automatically (see the advanced section).
Setting environment variables with PM2 ecosystem files
For any production application, the ecosystem file is the right approach. It gives you a single config file that defines your app, its environment variables, and how PM2 should run it.
Basic env, env_production, and env_development blocks
// ecosystem.config.js
module.exports = {
apps: [
{
name: "my-app",
script: "./app.js",
// Default env. Used when no --env flag is passed
env: {
NODE_ENV: "development",
PORT: 3000,
APP_LOG_LEVEL: "debug",
},
// Used with: pm2 start ecosystem.config.js --env production
env_production: {
NODE_ENV: "production",
PORT: 3000,
APP_LOG_LEVEL: "info",
},
// Used with: pm2 start ecosystem.config.js --env staging
env_staging: {
NODE_ENV: "staging",
PORT: 3000,
APP_LOG_LEVEL: "warn",
},
},
],
};
Each env_* block corresponds to a name you pass with --env. The base env block is the default when no --env flag is used.
Switching environments with –env [name]
# Start with development env (default):
pm2 start ecosystem.config.js
# Start with production env:
pm2 start ecosystem.config.js --env production
# Start with staging env:
pm2 start ecosystem.config.js --env staging
Ecosystem env vars update automatically on restart
When you use an ecosystem file, PM2 re-reads the file on pm2 restart. So if you edit the env_production block and run pm2 restart ecosystem.config.js --env production, the new values are picked up. This is different from CLI-set variables, which are conservative by default (more on that below).
Modern ecosystem file formats and scaffolding
PM2 supports more than just .js files:
ecosystem.config.js/ecosystem.config.cjs/ecosystem.config.mjsecosystem.json/ecosystem.json5ecosystem.yaml/ecosystem.yml
To scaffold a starter config:
pm2 init simple
This creates a basic ecosystem.config.js you can fill in.
Verify:
pm2 start ecosystem.config.js --env production
pm2 env 0 # Confirm NODE_ENV=production appears
If the wrong env block values appear, the --env name doesn’t match your ecosystem file key. --env prod will not match env_production. The name must match exactly.
How to update environment variables for a running PM2 application
This is where most people get stuck. PM2 is conservative by default: environment variables are essentially immutable once a process starts.
Restarts are conservative by default
From the official PM2 docs: “Via CLI, the environment is conservative meaning that, when you will run different process management actions (restart, reload, stop/start), new environment variables will not be updated into your application.” You must explicitly opt in with --update-env.
The –update-env flag
To change a variable for a CLI-started process:
# Set the new value and restart with --update-env
PORT=4000 pm2 restart my-app --update-env
To confirm the change took effect:
pm2 env 0 # Look for PORT=4000
For ecosystem file changes, just restart with the same --env flag:
# Edit env_production in ecosystem.config.js, then:
pm2 restart ecosystem.config.js --env production
Ecosystem file variables are always updated on restart. You don’t need --update-env for those.
Full delete + restart for stubborn variables
Some variables, most notably NODE_ENV, are read once at startup and won’t change even with --update-env. When that happens, delete and re-add is the only reliable path:
pm2 delete my-app
pm2 start ecosystem.config.js --env production
This is a known behavior. Community reports (GitHub issues #3192, #4135, #5591) confirm that --update-env can be unreliable in certain edge cases. When in doubt, delete and re-add.
The pm2 save / pm2 resurrect footgun
If you use pm2 save to persist your process list for auto-restart on reboot (via pm2 startup), be aware: pm2 save freezes the current environment variables into the dump file. When you later run pm2 resurrect, those saved values are used, even if you’ve changed the ecosystem file.
The fix: after changing env vars, delete and re-add your processes, then save again:
pm2 delete my-app
pm2 start ecosystem.config.js --env production
pm2 save
pm2 set Does NOT Update Application Env Vars
pm2 set is for PM2’s internal configuration system (e.g., pm2 set pm2:sysmonit true to toggle host monitoring). It does not set application environment variables and does not send any signal to your process. If you’ve been using pm2 set app:KEY value, that value is not reaching your application.
Advanced PM2 environment variable features
These features matter most when running multiple instances or deploying to multiple environments on the same server.
filter_env: prevent global env var leaks
By default, your PM2 process inherits all environment variables from the parent shell. That includes system vars, vars from other apps, and anything exported in your .bashrc. filter_env lets you whitelist a prefix.
module.exports = {
apps: [
{
name: "my-app",
script: "./app.js",
// Only pass env vars that start with APP_ or DB_
filter_env: ["APP_", "DB_"],
},
],
};
To drop all global env vars entirely:
filter_env: true,
This is a security feature. It prevents accidental leakage of secrets or variables from other processes. See the security section for more.
increment_var: auto-increment PORT per cluster instance
When running in PM2 cluster mode, each instance needs its own port. Instead of hardcoding ports, use increment_var:
module.exports = {
apps: [
{
name: "api",
script: "./api.js",
instances: 4,
exec_mode: "cluster",
increment_var: "PORT",
env: {
PORT: 3000,
},
},
],
};
This produces: instance 0 gets PORT=3000, instance 1 gets PORT=3001, instance 2 gets PORT=3002, instance 3 gets PORT=3003.
instance_var: customize NODE_APP_INSTANCE
PM2 sets NODE_APP_INSTANCE for each cluster instance (0, 1, 2, …). This conflicts with the node-config library, which uses the same variable for its own purposes. Rename it:
instance_var: "INSTANCE_ID",
Now each instance gets INSTANCE_ID=0, INSTANCE_ID=1, etc. instead of NODE_APP_INSTANCE.
append_env_to_name: multi-environment on one server
If you want to run the same application in development and production on a single machine:
module.exports = {
apps: [
{
name: "my-app",
script: "./app.js",
append_env_to_name: true,
env: { NODE_ENV: "development" },
env_production: { NODE_ENV: "production" },
},
],
};
With pm2 start ecosystem.config.js --env production, PM2 names the process my-app-production. With no flag, it’s my-app-development. Both can run simultaneously.
Listing and inspecting environment variables in PM2
To see all environment variables for a running process:
# By process ID
pm2 env 0
# By process name
pm2 env my-app
This shows every environment variable the process can see, both the ones you set and all the inherited system variables.
For JSON output (useful for scripting):
pm2 jlist | jq '.[0].pm2_env'
For a formatted view of all processes:
pm2 prettylist
Secrets Are Visible in pm2 env Output
Anyone with shell access can run pm2 env <id> and see all environment variables, including database passwords, API keys, and tokens. This is another reason to use filter_env and to restrict SSH access. For broader guidance on monitoring your application processes and server security, see the linked guides.
Security best practices for PM2 environment variables
- Never hardcode secrets in ecosystem files committed to git
- Use
filter_envto limit which variables your process can see - Inject secrets at deploy time, not at development time
- Restrict SSH access.
pm2 envexposes everything to anyone with a shell - Rotate secrets regularly and restart with
--update-envor delete+start - Audit your environment periodically with
pm2 env <id>
Never hardcode secrets in ecosystem files
Your ecosystem.config.js should be in version control. That means it should never contain real passwords, API keys, or tokens. Instead, inject them at deploy time:
Option 1: Wrapper script that sources a .env file
#!/bin/bash
# start-app.sh
set -a
source /etc/my-app/secrets.env
set +a
pm2 start ecosystem.config.js --env production
Option 2: Systemd environment file
If PM2 is managed by systemd (via pm2 startup), you can set variables in /etc/default/my-app and reference them in the unit file.
Option 3: Secrets manager
For teams and more complex setups, tools like Infisical, Doppler, or HashiCorp Vault can inject secrets at deploy time. For simple solo setups, a .env file with restricted permissions (chmod 600) sourced by a wrapper script is usually enough.
For readers managing secrets in containerized environments, see Docker Compose secrets management for a comparison of approaches.
Using filter_env to limit exposure
If your server runs multiple PM2 applications, filter_env prevents one app from seeing another app’s variables. Prefix-based filtering is the most practical:
filter_env: ["MYAPP_"],
Now only variables starting with MYAPP_ reach your process. Everything else is stripped.
Broader server security
Environment variables are only one part of the picture. If you haven’t already, review securing your SSH server in Linux. If someone gets shell access, all your env vars are exposed regardless of how well you manage them in PM2.
Common PM2 environment variable pitfalls (and how to avoid them)
--env selects environment blocks, not individual variables
pm2 start app.js --env PORT=3000 does not set PORT. The --env flag selects a named environment block from an ecosystem file. Use --env production to use the env_production block. To set individual variables on the command line, use the Unix prepend syntax: PORT=3000 pm2 start app.js.
Restarts don't pick up new env vars
PM2 is conservative by default. Running pm2 restart my-app will not pick up new environment variables from the shell. You must add --update-env: PORT=4000 pm2 restart my-app --update-env. For ecosystem file variables, restart with the ecosystem file and --env flag. Those are always refreshed.
NODE_ENV won't change with --update-env
NODE_ENV is read once at application startup. Even --update-env won’t change it. The only reliable way is the nuclear option: pm2 delete my-app followed by pm2 start ecosystem.config.js --env production (or the CLI equivalent with the new NODE_ENV value).
pm2 save freezes env vars into the dump file
When you run pm2 save, PM2 dumps the current process list, including all environment variables, to ~/.pm2/dump.pm2. On pm2 resurrect (often triggered automatically by pm2 startup), those saved values are used. If you changed env vars in the ecosystem file after saving, the resurrected processes will still use the old values. Delete, re-add, and save again.
Secrets are visible in pm2 env output
Any user with shell access can run pm2 env <id> and see all environment variables, including database passwords and API keys. Use filter_env to limit what your process inherits, and restrict SSH access to the server.
pm2 set is for PM2 internal config, not application vars
pm2 set pm2:sysmonit true toggles PM2’s host monitoring. pm2 set myapp:PORT 3000 does not set an application environment variable. There is no pm2 set command that affects process.env in your application. Use ecosystem files or CLI prepend + --update-env instead.
Conclusion
PM2 environment variables come down to a few core rules:
- Use ecosystem files for production. They’re explicit, version-controllable, and PM2 re-reads them on restart.
- Use
--update-envwhen changing CLI-set variables. Without it,pm2 restartis conservative and won’t pick up changes. - Delete and re-start for
NODE_ENVchanges.--update-envdoesn’t work for variables read at startup. - Use
filter_envto limit exposure. Don’t let your app inherit the entire server environment. - Test with
pm2 env <id>. Always verify what your process actually sees.
For the full process management picture, see the complete guide to managing applications with PM2 and choosing between fork and cluster mode. And if your Node.js dependencies are behind, keeping your Node.js environment up to date is worth the effort. PM2 7.x requires Node.js 18+.
If you’re looking for an affordable VPS to run your PM2-managed apps, Hetzner Cloud offers solid price-to-performance for European and US regions. Hostinger VPS is another budget option with NVMe storage.


