Bitdoze Logo

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.

DragosDragos15 min read
PM2 Environment Variables: Setup, Update & Best Practices

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.

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.mjs
  • ecosystem.json / ecosystem.json5
  • ecosystem.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_env to limit which variables your process can see
  • Inject secrets at deploy time, not at development time
  • Restrict SSH access. pm2 env exposes everything to anyone with a shell
  • Rotate secrets regularly and restart with --update-env or 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:

  1. Use ecosystem files for production. They’re explicit, version-controllable, and PM2 re-reads them on restart.
  2. Use --update-env when changing CLI-set variables. Without it, pm2 restart is conservative and won’t pick up changes.
  3. Delete and re-start for NODE_ENV changes. --update-env doesn’t work for variables read at startup.
  4. Use filter_env to limit exposure. Don’t let your app inherit the entire server environment.
  5. 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.