✨ Strapi MCP is now Generally Available - let your agents manage your Strapi content ✨

npm run start runs Strapi in the foreground. Close the SSH session and the site goes down. Crash the process and nothing brings it back. Reboot the server and you are logging in to start it by hand.

A process manager fixes all three. PM2 is the usual choice for Node applications, and the Strapi docs recommend it for self-hosted deployments.

Install

npm install pm2 -g
# or
yarn global add pm2

Three ways to start Strapi under PM2

1. With a server.js file

Create server.js at the project root:

const strapi = require('@strapi/strapi');

strapi.createStrapi().start();
pm2 start server.js --name strapi

TypeScript projects need distDir. A TypeScript project compiles to ./dist, and the factory has to be pointed at it:

const strapi = require('@strapi/strapi');

strapi.createStrapi({ distDir: './dist' }).start();

Without that, PM2 starts a process that loads no application code and exits or serves nothing.

Upgrading from Strapi v4: v4 used strapi({ distDir: './dist' }).start(). Strapi 5 exposes the factory as createStrapi. A copied v4 server.js will throw strapi is not a function.

2. With the strapi command

No extra file needed:

pm2 start npm --name strapi -- run start

or:

pm2 start "npx strapi start" --name strapi

Simple, and it goes through your package.json scripts, so whatever npm run start does locally is what runs in production.

This is the version worth using for anything real, because the configuration lives in the repository rather than in shell history.

module.exports = {
  apps: [
    {
      name: 'strapi',
      cwd: '/var/www/strapi',
      script: 'npm',
      args: 'run start',
      instances: 1,
      exec_mode: 'fork',
      autorestart: true,
      max_memory_restart: '1G',
      watch: false,
      env: {
        NODE_ENV: 'production',
        HOST: '0.0.0.0',
        PORT: 1337,
        PUBLIC_URL: 'https://cms.example.com',
      },
      error_file: '/var/log/strapi/error.log',
      out_file: '/var/log/strapi/out.log',
      merge_logs: true,
      time: true,
    },
  ],
};
pm2 start ecosystem.config.js

Note exec_mode: 'fork' and instances: 1. This matters, and the next section explains why.

Do not use PM2 cluster mode without reading this

PM2's cluster mode forks multiple Node processes sharing a port. It is the default advice for scaling Node apps and it is a trap for Strapi.

Each Strapi instance loads the schema, runs migrations, and performs a database schema sync at startup. Several instances doing that simultaneously against the same database race each other, and the failure modes range from duplicate migration attempts to a corrupted schema state.

Beyond boot, cluster mode also breaks:

  • Local file uploads: each worker writes to its own view of public/uploads, and requests are load-balanced, so uploads appear to vanish. Use S3, Cloudinary, or another shared provider.
  • In-memory caching: not shared between workers.
  • Cron jobs: every worker runs them, so a nightly job runs N times.

If you need to run more than one instance:

  1. Use a shared upload provider.
  2. Run migrations once, as a separate deploy step, before starting any instance.
  3. Run cron on exactly one instance, gated behind an environment variable.
  4. Prefer separate PM2 apps on distinct ports behind Nginx over cluster mode, so each instance is individually addressable.

Realistically, most Strapi deployments run one instance per machine and scale horizontally across machines.

Secrets do not belong in the ecosystem file

The env block above shows non-secret values. Do not put DATABASE_PASSWORD, APP_KEYS, ADMIN_JWT_SECRET, JWT_SECRET, API_TOKEN_SALT, or ENCRYPTION_KEY in a file you commit.

Keep them in .env on the server (readable only by the service user), and PM2 will pick them up because Strapi loads it, or use env_file:

module.exports = {
  apps: [
    {
      name: 'strapi',
      cwd: '/var/www/strapi',
      script: 'npm',
      args: 'run start',
      env_file: '/var/www/strapi/.env',
      env: {
        NODE_ENV: 'production',
      },
    },
  ],
};
chmod 600 /var/www/strapi/.env
chown strapi:strapi /var/www/strapi/.env

Surviving a reboot

Two separate steps, and people routinely do only the first:

# 1. Generate and install the systemd/launchd startup script
pm2 startup
# Run the command it prints, which needs sudo

# 2. Save the current process list as the one to resurrect
pm2 save

Without pm2 save, PM2 starts on boot with an empty process list. Re-run pm2 save whenever you add or change an app.

Deploying an update

The order matters. Build before restarting, or you will serve a half-updated admin panel:

cd /var/www/strapi
git pull
npm ci
NODE_ENV=production npm run build
pm2 restart strapi --update-env

--update-env re-reads environment variables. Without it, PM2 restarts the process with the environment it captured the first time, which is a genuinely baffling bug to chase when you have just changed a variable.

For zero-downtime updates you need two instances behind Nginx and pm2 reload; with a single instance, restart means a few seconds of 502s. If that matters, drain the instance from the load balancer first.

Logs

pm2 logs strapi              # tail
pm2 logs strapi --lines 200  # recent history
pm2 flush                    # clear

Strapi is chatty. Install log rotation on day one, before the disk fills:

pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true

Monitoring

pm2 status          # process list
pm2 monit           # live CPU and memory
pm2 describe strapi # full detail, including restart count

A climbing restart count in pm2 describe means the process is crash-looping. Check pm2 logs for the reason; it is usually a database connection failure or a missing environment variable at boot.

Pair PM2 with Strapi's own health endpoint for external monitoring:

curl -I https://cms.example.com/_health   # → 204

max_memory_restart is a bandage

max_memory_restart: '1G' restarts the process when it exceeds a gigabyte. It keeps a leaking service available, and it hides the leak. If it fires regularly, find out why: large image processing with sharp and unbounded query results are the two usual causes in Strapi.

Command reference

TaskCommand
Startpm2 start ecosystem.config.js
Stoppm2 stop strapi
Restart with fresh envpm2 restart strapi --update-env
Reload (zero-downtime, needs >1 instance)pm2 reload strapi
Remove from PM2pm2 delete strapi
Statuspm2 status
Logspm2 logs strapi
Persist across rebootpm2 startup then pm2 save

What about Docker?

If you are already running Strapi in a container, you do not need PM2. The container runtime, whether Docker's restart policy, Kubernetes, or ECS, is your process manager. Adding PM2 inside a container gives you two supervisors disagreeing about restarts and hides crashes from the orchestrator.

Run Node directly as PID 1 in the container and let the platform handle restarts.

Further reading

Theodore Kelechukwu OnyejiakuDevRel and Community | Software Developer | Technical Writer

Theodore is a Technical Writer and a full-stack software developer. He loves writing technical articles, building solutions, and sharing his expertise.

"Cannot destructure property 'client' of 'db.config.connection'" in Strapi
EcosystemIntermediate·6 min read

"Cannot destructure property 'client' of 'db.config.connection'" in Strapi

This error means Strapi loaded no database configuration at all. The file is missing from the build, excluded from the container, or never compiled.

·September 9, 2026
"Cannot send secure cookie over unencrypted connection" in Strapi
EcosystemAdvanced·6 min read

"Cannot send secure cookie over unencrypted connection" in Strapi

Your site is on HTTPS but Strapi thinks the request came over HTTP. The fix is one config key, proxy.koa, plus the proxy headers that make it work.

·September 9, 2026
"Strapi is in production mode" when you are running development mode
EcosystemBeginner·5 min read

"Strapi is in production mode" When you are Running Development Mode

The Content-Type Builder is disabled and Strapi claims production, but your terminal says development. The rule Strapi actually applies, and the fix.

·September 9, 2026