Skip to content

TutorialsCMS & websites

How to install Strapi 5 with PostgreSQL, PM2 and HTTPS

Run Strapi 5 in production on Ubuntu or Debian with PostgreSQL, Node.js LTS and PM2 as a non-root user, behind Caddy HTTPS, with backups and upgrades.

  • Intermediate
  • 45 min read
  • Updated

Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13

This guide is not available in your language yet, so it is shown in English.

On this page
  1. Prerequisites
  2. Step 1 — Install PostgreSQL and create the database
  3. Step 2 — Install Node.js LTS and PM2
  4. Step 3 — Create the strapi user and the project
  5. Step 4 — Configure production settings
  6. Step 5 — Build, start with PM2 and create the first admin
  7. Step 6 — Publish Strapi over HTTPS with Caddy
  8. Step 7 — Change content types in development, then deploy
  9. Back up and restore
  10. Update Strapi
  11. Troubleshooting
  12. The admin panel answers with a 500 error on a fresh install
  13. The build stops with JavaScript heap out of memory or the process is killed
  14. The Content-Type Builder is disabled on the server
  15. Admin requests go to localhost or plain HTTP
  16. Strapi does not come back after a reboot
  17. Next steps

Strapi is an open-source headless CMS built on Node.js. Editors manage content in an admin panel, and your websites and apps read it through REST or GraphQL APIs. This guide runs Strapi 5 in production on a single server: PostgreSQL as the database, Node.js LTS from the NodeSource repository, PM2 as the process manager under a dedicated non-root user, and Caddy in front for automatic HTTPS. You also set up the development-to-production workflow Strapi expects, plus backups, upgrades and troubleshooting.

Prerequisites

  • A server running Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12 or Debian 13. Strapi's requirements name Ubuntu 24.04 as the recommended Ubuntu release (20.04 is the minimum) and Debian 10 as the minimum Debian release.
  • A non-root user with sudo rights and SSH key login. If you have not done this yet, follow Secure a new Linux server and Set up SSH keys first.
  • A domain name such as cms.example.com with an A record (and optionally an AAAA record) pointing at the server.
  • Caddy installed as described in Caddy reverse proxy. You need it in Step 6.

Strapi publishes its own hardware requirements:

ResourceMinimum (official)Suggested starting point
CPU1 core2 cores (Strapi's recommendation)
RAM2 GB4 GB (Strapi's recommendation)
Disk8 GB32 GB (Strapi's recommendation), plus space for uploads

Strapi supports PostgreSQL 14 or newer and recommends 17. The PostgreSQL package that ships with each release above meets the minimum. Building the admin panel is the heaviest job on a small server; if a build gets killed, see Troubleshooting.

Step 1 — Install PostgreSQL and create the database

Install PostgreSQL from your distribution's archive and check that the service runs:

Bash
sudo apt update
sudo apt install postgresql
sudo systemctl status postgresql --no-pager

On Ubuntu and Debian PostgreSQL listens only on localhost by default, so it needs no firewall rule. Generate a strong password for the database user and keep it for Step 3:

Bash
openssl rand -hex 24

Create a strapi role (paste the password when prompted), a database owned by it, and grant the schema permission Strapi's documentation asks for. Without the GRANT, a fresh database user cannot create tables in the public schema and the admin panel answers with a 500 error:

Bash
sudo -u postgres createuser --pwprompt strapi
sudo -u postgres createdb --owner=strapi strapi
sudo -u postgres psql -d strapi -c "GRANT ALL ON SCHEMA public TO strapi;"

Test the login over TCP, the way Strapi will connect. After you enter the password, the query prints strapi:

Bash
psql -h 127.0.0.1 -U strapi -d strapi -c "SELECT current_user;"

Step 2 — Install Node.js LTS and PM2

On most of these releases the distribution's own nodejs package is older than Node.js 22, the oldest line Strapi supports. You have two common alternatives: nvm, which installs Node.js per user inside a home directory and is handy on a development machine where you switch versions often, and the NodeSource apt repository, which installs one system-wide Node.js under /usr/bin and updates it with apt together with your other security updates. For a server that runs a long-lived service, the apt repository is easier to keep patched and does not depend on a shell profile, so this guide uses NodeSource.

NodeSource's manual installation uses a distribution-independent suite called nodistro. Add the signing key and the repository for Node.js 24 LTS:

Bash
sudo apt install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
Bash
NODE_MAJOR=24
sudo tee /etc/apt/sources.list.d/nodesource.sources <<EOF
Types: deb
URIs: https://deb.nodesource.com/node_${NODE_MAJOR}.x/
Suites: nodistro
Components: main
Signed-By: /etc/apt/keyrings/nodesource.gpg
EOF
sudo apt update
sudo apt install nodejs
node -v
npm -v

node -v should print a v24 version. Now install PM2 globally, so the pm2 command is on every user's path and the boot script can find it:

Bash
sudo npm install -g pm2
pm2 --version

Step 3 — Create the strapi user and the project

Run Strapi under its own system user, never as root. The user's home directory /srv/strapi holds the project and PM2's state:

Bash
sudo adduser --system --group --home /srv/strapi --shell /bin/bash strapi
sudo -iu strapi

You are now in a shell as strapi. Create the project with the official create-strapi tool. The flags answer the interactive questions in advance: skip the Strapi Cloud login, use npm and JavaScript, install dependencies, start a Git repository, skip example data and anonymous A/B tests, and connect to the PostgreSQL database from Step 1. Replace change-me with the password you generated:

Bash
cd /srv/strapi
npx create-strapi@latest app --skip-cloud --no-run --javascript --use-npm --install --no-example --git-init --no-enable-ab-tests --dbclient=postgres --dbhost=127.0.0.1 --dbport=5432 --dbname=strapi --dbusername=strapi --dbpassword=change-me

npx asks once whether it may download create-strapi; answer y. Answer any question the flags did not cover. Use --typescript instead of --javascript if your team prefers TypeScript; the configuration file in Step 4 is then config/server.ts with export default instead of module.exports. When the command finishes, list the project:

Bash
cd /srv/strapi/app
ls -a

You should see config, src, public, package.json and a .env file.

Step 4 — Configure production settings

create-strapi writes a .env file with freshly generated values for APP_KEYS, API_TOKEN_SALT, ADMIN_JWT_SECRET, TRANSFER_TOKEN_SALT, JWT_SECRET and ENCRYPTION_KEY, plus the database settings. List the variable names without printing the secrets:

Bash
cut -d= -f1 .env

Open the file with nano .env and make sure it contains these lines. Bind Strapi to 127.0.0.1 so only Caddy can reach it, set your public URL, and turn off telemetry if you prefer:

.env
HOST=127.0.0.1
PORT=1337
PUBLIC_URL=https://cms.example.com
APP_KEYS=generated-value-1,generated-value-2,generated-value-3,generated-value-4
API_TOKEN_SALT=generated-value
ADMIN_JWT_SECRET=generated-value
TRANSFER_TOKEN_SALT=generated-value
JWT_SECRET=generated-value
ENCRYPTION_KEY=generated-value
DATABASE_CLIENT=postgres
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
DATABASE_NAME=strapi
DATABASE_USERNAME=strapi
DATABASE_PASSWORD=change-me
DATABASE_SSL=false
STRAPI_TELEMETRY_DISABLED=true

If a secret is missing, for example because you cloned an existing project whose .env is not in Git, generate each value with openssl rand -base64 32; APP_KEYS takes several such values separated by commas. Then restrict the file to its owner:

Bash
chmod 600 .env

Next, tell Strapi its public URL and that it runs behind one reverse proxy. Open config/server.js with nano config/server.js and replace its content with:

Text
module.exports = ({ env }) => ({
  host: env('HOST', '0.0.0.0'),
  port: env.int('PORT', 1337),
  url: env('PUBLIC_URL', 'https://cms.example.com'),
  proxy: {
    koa: true,
    maxIpsCount: 1,
  },
  app: {
    keys: env.array('APP_KEYS'),
  },
});

proxy.koa: true makes Strapi trust the X-Forwarded-* headers Caddy sends, and maxIpsCount: 1 limits that trust to exactly one proxy. Strapi's Caddy guide notes that leaving maxIpsCount unset means an unlimited count. Changes to this file need a fresh admin build, which you run next.

Step 5 — Build, start with PM2 and create the first admin

Build the admin panel. Set NODE_ENV on the build command itself only: Strapi's PM2 guide warns that exporting it for the whole session makes npm skip the development dependencies the build needs.

Bash
NODE_ENV=production npm run build

Create the PM2 configuration with nano ecosystem.config.js in /srv/strapi/app:

Text
module.exports = {
  apps: [
    {
      name: 'strapi',
      cwd: '/srv/strapi/app',
      script: 'npm',
      args: 'start',
      env: {
        NODE_ENV: 'production',
      },
    },
  ],
};

npm start runs strapi start, the production mode without auto-reload. Start the app and check its health endpoint, which answers HTTP/1.1 204 No Content once Strapi is ready:

Bash
pm2 start ecosystem.config.js
pm2 list
curl -I http://127.0.0.1:1337/_health

Until an administrator exists, the /admin page shows a registration form to anyone who opens it. Create the first admin from the command line before the site goes public. Run the command without options and answer the prompts for name, email and password:

Bash
npx strapi admin:create-user

Now make PM2 start at boot. Leave the strapi shell, generate the systemd unit for the strapi user, and save the current process list:

Bash
exit
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u strapi --hp /srv/strapi
sudo -iu strapi pm2 save
systemctl status pm2-strapi --no-pager

The unit is called pm2-strapi and should be active. Run pm2 save again whenever you add or rename an app. Finally, keep PM2's own logs from filling the disk, as the Strapi PM2 guide suggests:

Bash
sudo -iu strapi pm2 install pm2-logrotate
sudo -iu strapi pm2 set pm2-logrotate:max_size 10M
sudo -iu strapi pm2 set pm2-logrotate:retain 7

Step 6 — Publish Strapi over HTTPS with Caddy

Add a site block to /etc/caddy/Caddyfile. Caddy obtains and renews the certificate on its own:

Caddyfile
cms.example.com {
    reverse_proxy 127.0.0.1:1337
}
Bash
sudo systemctl reload caddy

Allow only SSH and web traffic through the firewall. Port 1337 stays closed because Strapi listens on 127.0.0.1 only:

Bash
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ss -tlnp | grep 1337

The last command should show 127.0.0.1:1337, not 0.0.0.0:1337. Check the public endpoint with curl -I https://cms.example.com/_health, then open https://cms.example.com/admin and log in with the account from Step 5. Prefer Nginx? Use Nginx with Certbot with the same upstream; Traefik works too.

Step 7 — Change content types in development, then deploy

On the server, Strapi runs in production mode, where the Content-Type Builder is disabled. Strapi's FAQ explains why: schemas are files such as src/api/article/content-types/article/schema.json, and changing them needs a restart. Treat the server as a deployment target:

  1. Push the project to a private Git repository. Strapi's generated .gitignore should exclude .env and node_modules; confirm with git check-ignore .env.
  2. Clone it on your workstation, run npm install and npm run develop, and design or change content types there.
  3. Commit, push, and deploy on the server.

To connect the server copy to your repository, run as strapi in /srv/strapi/app (the strapi user needs read access, for example a read-only deploy key):

Bash
git status
git remote add origin [email protected]:team/cms.git
git push -u origin HEAD

Each deployment then looks like this:

Bash
sudo -iu strapi
cd /srv/strapi/app
git pull
npm ci
NODE_ENV=production npm run build
pm2 restart strapi --update-env

Strapi synchronises the database schema when it starts, so a removed field means removed data. Take a backup before you deploy schema changes.

Back up and restore

Three things hold state: the PostgreSQL database, uploaded files in public/uploads (the default local upload provider), and the secrets in .env. The code itself lives in Git. Create a private backup folder and dump everything with dated names:

Bash
sudo mkdir -p /opt/backups
sudo chown $USER:$USER /opt/backups
chmod 700 /opt/backups
sudo -u postgres pg_dump -Fc strapi > /opt/backups/strapi-db-$(date +%F).dump
sudo tar czf /opt/backups/strapi-files-$(date +%F).tar.gz -C /srv/strapi/app public/uploads .env

pg_dump produces a consistent snapshot while Strapi keeps running. Strapi's own strapi export command is useful for moving content between environments, but it does not include admin users or API tokens, so it is an extra, not a replacement. If you want one, run it as strapi (the archive is unencrypted, so store it as carefully as the database dump):

Bash
sudo -iu strapi
cd /srv/strapi/app
npm run strapi export -- --no-encrypt -f /srv/strapi/export-$(date +%F)
exit

To restore, stop Strapi, recreate the database, load the dump, unpack the files and start again. Replace the dates with those of your backup:

Bash
sudo -iu strapi pm2 stop strapi
sudo -u postgres dropdb strapi
sudo -u postgres createdb --owner=strapi strapi
sudo -u postgres pg_restore -d strapi < /opt/backups/strapi-db-2026-10-09.dump
sudo tar xzf /opt/backups/strapi-files-2026-10-09.tar.gz -C /srv/strapi/app
sudo chown -R strapi:strapi /srv/strapi/app/public/uploads /srv/strapi/app/.env
sudo -iu strapi pm2 start strapi

On a new server, run Steps 1 to 3 first (cloning your repository instead of creating a project), then restore. Copy backups off the server as well, for example with rsync to another machine, and test a restore now and then.

Update Strapi

Upgrade Strapi in development, not on the server, because the official upgrade tool edits your code. Read the release notes and back up first. In your development copy, preview the changes with a dry run, then apply them:

Bash
npx @strapi/upgrade minor --dry
npx @strapi/upgrade minor

patch and minor stay within your current major version. major moves to the next major but requires the latest minor and patch of your current one; latest can also cross majors after a confirmation. The tool updates dependencies and runs codemods; it does not migrate data. Review the changes, test with npm run develop, commit, and deploy with the routine from Step 7.

Node.js patch releases arrive with sudo apt update && sudo apt upgrade. To move to a new Node.js major line, change NODE_MAJOR in the repository file to another version Strapi supports, upgrade, rebuild, and regenerate PM2's boot script as PM2's documentation advises: run pm2 unstartup, then the pm2 startup command from Step 5 again. Update PM2 itself with sudo npm install -g pm2, then run pm2 update as strapi.

Troubleshooting

The admin panel answers with a 500 error on a fresh install

The database user cannot create tables in the public schema. Run the GRANT ALL ON SCHEMA public TO strapi; command from Step 1, then pm2 restart strapi as the strapi user.

The build stops with JavaScript heap out of memory or the process is killed

The admin build needs more memory than is free. Stop other heavy processes, add a temporary swap file, or move to a server with at least the 4 GB Strapi recommends. Check free -h and dmesg | tail for out-of-memory kills.

The Content-Type Builder is disabled on the server

This is expected in production mode. Create and change content types in a development copy and deploy them as described in Step 7.

Admin requests go to localhost or plain HTTP

PUBLIC_URL is missing or wrong, or the admin panel was built before you changed config/server.js. Fix .env and the url setting, rebuild with NODE_ENV=production npm run build, and run pm2 restart strapi --update-env.

Strapi does not come back after a reboot

The boot unit is missing or the process list was not saved. Check systemctl status pm2-strapi, repeat the pm2 startup and pm2 save commands from Step 5, and read the logs with pm2 logs strapi --lines 100 as strapi.

Next steps

Frequently asked questions

Can I create or edit content types on the production server?

No. Strapi stores content-type schemas as files in the project and disables the Content-Type Builder when it runs in production mode. Make schema changes in a development copy with npm run develop, commit them to Git, then pull, build and restart on the server.

Which Node.js version does Strapi 5 support?

Strapi supports only Active LTS and Maintenance LTS releases of Node.js; its deployment page lists versions 22, 24 and 26. Odd-numbered releases such as 23 or 25 are not supported. This guide installs Node.js 24 from the NodeSource repository.

Should I use PM2 or a plain systemd service?

Strapi's deployment documentation highly recommends a process manager such as PM2. PM2's startup command generates a systemd unit, so you get both: PM2 keeps Strapi running and systemd starts PM2 at boot under the non-root strapi user.

Is strapi export a full backup?

No. The export archive contains content, uploaded files, configuration and schemas, but Strapi documents that admin users and API tokens are not exported. Use pg_dump plus an archive of public/uploads and .env as your real backup, and keep exports for moving content between environments.

Can I use SQLite instead of PostgreSQL?

SQLite is supported and is the default for new projects, but this guide uses PostgreSQL so the database runs as its own service and can be dumped with pg_dump while Strapi keeps running. MongoDB and other NoSQL databases are not supported.

Which HyperDC servers can run Strapi?

Any HyperDC Linux VPS, VDS or dedicated server with root access and a supported Ubuntu or Debian release. Strapi lists 2 GB of RAM as the minimum and 4 GB or more as recommended, so size the server for the admin build as well as for traffic.

Sources

Gerar Senha

Please confirm