Websites & apps

Deploy Strapi headless CMS on a VPS with PM2 and Nginx

Run Strapi in production on a Linux VPS: PostgreSQL or MySQL, environment variables and secrets, a production build, PM2 process management, Nginx with SSL and uploads.

IntermediateLinux VPS3 min read

Strapi is an open-source headless CMS built on Node.js: editors manage content in its admin panel, and your frontend reads it through REST or GraphQL. This guide shows how to deploy Strapi on a VPS with a real database, PM2 and Nginx.

Before you start

  • A Linux VPS with 2 GB RAM or more (the admin build needs memory) and sudo access.
  • A Node.js LTS version supported by your Strapi release (check its docs), installed for example via nvm or NodeSource.
  • PostgreSQL or MySQL/MariaDB with a database and user for Strapi.
  • A subdomain such as cms.example.com pointing to the VPS.

Get the project on the server

Develop locally with npx create-strapi@latest, commit to Git (content types live in code), then on the server:

git clone git@github.com:you/cms.git /srv/cms && cd /srv/cms && npm ci

Install the database driver if not already a dependency: npm install pg (PostgreSQL) or npm install mysql2.

Configure environment variables

Create /srv/cms/.env (chmod 600), generating each secret with openssl rand -base64 32:

HOST=127.0.0.1
PORT=1337
NODE_ENV=production
PUBLIC_URL=https://cms.example.com
APP_KEYS=key1,key2,key3,key4
API_TOKEN_SALT=...
ADMIN_JWT_SECRET=...
TRANSFER_TOKEN_SALT=...
JWT_SECRET=...
DATABASE_CLIENT=postgres
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
DATABASE_NAME=strapi
DATABASE_USERNAME=strapi
DATABASE_PASSWORD=strong-password

Make sure config/server uses url: env('PUBLIC_URL') so admin links and the admin panel work behind the proxy. Keep secrets out of Git.

Build for production

NODE_ENV=production npm run build

This compiles the admin panel. If the build is killed on a small VPS, add swap or build locally/in CI and copy the result.

Run Strapi with PM2

npm install -g pm2
cd /srv/cms && NODE_ENV=production pm2 start npm --name strapi -- run start

Save the process list and enable start on boot:

pm2 save && pm2 startup

Run the command pm2 startup prints. Check logs with pm2 logs strapi.

Configure Nginx and SSL

server {
    server_name cms.example.com;
    client_max_body_size 100m;

    location / {
        proxy_pass http://127.0.0.1:1337;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

client_max_body_size allows large media uploads. Enable SSL with certbot --nginx -d cms.example.com, then open https://cms.example.com/admin and create the first administrator.

Media uploads and backups

  • By default uploads are stored in public/uploads; back that folder up with the database. For multiple servers or large libraries use an S3-compatible upload provider.
  • Dump the database daily (pg_dump or mysqldump).
  • Strapi's strapi export command creates an encrypted archive of content and files.

Deploy updates

cd /srv/cms && git pull && npm ci && NODE_ENV=production npm run build && pm2 restart strapi

Troubleshooting

  • Admin panel loads blank or with wrong URLs: PUBLIC_URL / url is not set, or the admin was built before you set it. Rebuild.
  • Build killed / JavaScript heap out of memory: add swap or set NODE_OPTIONS=--max-old-space-size=2048.
  • 413 Request Entity Too Large on upload: raise client_max_body_size in Nginx.
  • Cannot create content types in production: by design; the Content-Type Builder works only in development. Create types locally and deploy them.

Strapi FAQ

Can I use SQLite in production?

Possible for tiny projects, but PostgreSQL or MySQL is recommended for reliability and backups.

Does Strapi run on cPanel hosting?

Strapi needs a long-running Node.js process and enough memory to build; a VPS is the reliable choice.

How do frontends access content?

Through the REST or GraphQL API, with API tokens created in Settings → API Tokens.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.