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.compointing 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 ciInstall 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-passwordMake 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 buildThis 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 pm2cd /srv/cms && NODE_ENV=production pm2 start npm --name strapi -- run startSave the process list and enable start on boot:
pm2 save && pm2 startupRun 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_dumpormysqldump). - Strapi's
strapi exportcommand 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 strapiTroubleshooting
- Admin panel loads blank or with wrong URLs:
PUBLIC_URL/urlis 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_sizein 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.
