Strapi CMS on AlmaLinux 9 (PostgreSQL + PM2 + Nginx + SSL)

Strapi is an open‑source, Node.js‑based headless CMS that exposes content via REST and GraphQL. On AlmaLinux 9—a RHEL 9–compatible distribution—you get a stable, long‑lifecycle platform suitable for hardened, enterprise deployments.

Stack at a Glance

LayerComponentPurpose
OSAlmaLinux 9 (kernel 5.14, SELinux enforcing)Enterprise stability and security baseline
RuntimeNode.js LTS (18/20)Runs Strapi application
DBPostgreSQL 14/15Primary data store (content, users, config)
Process mgrPM2Keeps Strapi online (restart, logs, clustering)
ProxyNginxTLS termination, HTTP/2, compression, routing
TLSLet’s Encrypt or custom PKIEncrypts admin/API traffic end‑to‑end

Why PostgreSQL for Strapi

  • JSONB columns for flexible content structures plus relational integrity.
  • Robust indexing and full‑text search options.
  • Strong concurrency and reliability under write/read load.
  • Mature tooling for replication, backups, and role management.

Recommended: separate DB node or VM for medium/large deployments, with WAL archiving and regular base backups.


PM2 in Production

  • Daemonizes Strapi, auto‑restarts on crash or OOM, and reboots with pm2 startup.
  • Cluster mode to utilize multiple CPU cores behind Nginx.
  • Centralized logs with rotation; expose metrics via pm2 monit or PM2 API.
  • Use an ecosystem file to pin environment variables, cwd, and instances per environment (dev/stage/prod).

Nginx as Reverse Proxy (with SSL)

  • Offloads TLS, serves HTTP/2, gzip/brotli, and static assets efficiently.
  • Path routing to Strapi (e.g., location / { proxy_pass http://127.0.0.1:1337; }).
  • Apply security headers (HSTS, CSP, X‑Frame‑Options, X‑Content‑Type‑Options).
  • Rate limiting or WAF rules to protect the admin panel and auth endpoints.
  • Use OCSP stapling and modern ciphers via OpenSSL 3 on AlmaLinux 9.

Security and Hardening on AlmaLinux 9

  • SELinux: keep enforcing; add minimal custom policy if Strapi writes outside expected directories.
  • FirewallD: expose only 80/443 publicly; restrict Strapi’s internal port (1337) to localhost.
  • Secrets management: store credentials in environment variables loaded by PM2; avoid committing .env to VCS.
  • RBAC in Strapi: lock down public and authenticated roles; separate admin users; enable email confirmations where appropriate.
  • Dependency hygiene: patch Node, Strapi, and plugins frequently; enable npm audit in CI.
  • Backups: schedule PostgreSQL dumps or base backups; back up uploads directory and environment configuration.
  • Fail2Ban: protect Nginx admin and auth routes from brute force.

Performance and Sizing

Small (MVP / pilot)

  • 2 vCPU, 4–8 GB RAM, single Strapi instance under PM2
  • PostgreSQL co‑located or managed service
  • Nginx on same host

Medium (team content ops, public APIs)

  • 4–8 vCPU, 8–16 GB RAM
  • PM2 cluster mode (2–4 instances) behind Nginx
  • Dedicated PostgreSQL VM (NVMe SSD, tuned shared_buffers, work_mem)
  • Object storage (S3‑compatible) for media via Strapi upload provider

Large/High traffic

  • Multiple Strapi nodes behind Nginx/Load Balancer
  • Managed or HA PostgreSQL (replication, Patroni/Cloud)
  • Redis cache for sessions/rate limiters if used by plugins
  • CDN in front of Nginx for static and uploads

General tips:

  • Set NODE_ENV=production, build admin once per release.
  • Cache public GETs at Nginx or CDN where safe.
  • Prefer S3/MinIO adapters for uploads to keep stateless app nodes.

Operations & Observability

  • Logging: PM2 and Strapi logs to systemd/journald or file; centralize with Fluentd/Vector to ELK/Opensearch.
  • Metrics: export Node/PM2 metrics to Prometheus; Nginx stub_status or exporter for proxy metrics; PostgreSQL exporter for DB health.
  • Health checks: simple / or /admin HTTP 200 checks via Nginx; use PM2 health or systemd watchdog for process supervision.
  • CI/CD: pin Node and package versions; build artifacts once; run DB migrations in controlled pipeline steps.

Typical Use Cases

  • Headless CMS for React/Next.js, Vue/Nuxt, SvelteKit, mobile apps.
  • Multichannel content APIs with i18n, drafts, and review workflows.
  • Product catalogs and marketing sites requiring editorial roles.
  • Internal data hubs where teams manage structured content via custom permissions.

Comparison Snapshot (self‑hosted)

CapabilityStrapiDirectusKeystoneJSPayload
DB supportPostgreSQL/MySQL/SQLitePostgreSQLPostgreSQL/MongoPostgreSQL
Admin UIBuilt‑in, refinedBuilt‑in, data‑firstBuilt‑inBuilt‑in
API stylesREST + GraphQL pluginREST + GraphQLREST + GraphQLREST + GraphQL
ExtensibilityPlugins, policies, lifecyclesHooks/flowsSchemas/hooksHooks/plugins
FilesLocal, S3, adaptersLocal, S3Local, S3Local, S3

Strapi balances a mature admin UI, strong plugin ecosystem, and straightforward extensibility with policies, controllers, and lifecycle hooks.


Practical Checklist

  • Choose Node LTS version and lock it; build the admin in CI.
  • Use PostgreSQL 14/15, tune memory and WAL for workload.
  • Run Strapi under PM2 with an ecosystem config; enable startup on boot.
  • Put Nginx in front; terminate TLS; restrict port 1337 to localhost.
  • Enforce RBAC and secure public permissions; review plugin access.
  • Externalize media to S3/MinIO for scalability; use a CDN if public.
  • Back up DB and uploads; test restores; rotate logs; patch regularly.

A Strapi + PostgreSQL + PM2 + Nginx + SSL stack on AlmaLinux 9 delivers a durable, secure, and scalable headless CMS platform. You gain enterprise‑grade OS stability (SELinux, long lifecycle) with modern Node tooling, robust relational storage, resilient process management, and a hardened TLS front end—well‑suited for both MVPs and high‑traffic production APIs.


Step 1: Create an AlmaLinux 9 VPS on Shape.Host

Go to https://shape.host and log in.

Click “Create”, then select “Instance”.

Shape.host Resources dashboard with Create > Instance highlighted.

Choose the server location closest to your target audience.

Shape.host instance form with the data center location selector highlighted.

Select AlmaLinux 9 (64-bit) as the operating system.

Pick a plan with at least 2 CPUs, 4 GB RAM, and 20 GB SSD.

Shape.host instance form with the operating system image selector highlighted.

Click “Create Instance”.

Shape.host instance form with the Create Instance button highlighted.

Copy the IP address from the “Resources” section.

Shape.host Resources dashboard with the server IP address highlighted.

Step 2: Connect to the Server

ssh root@your-server-ip

Step 3: Update System and Install Base Packages

dnf update -y
dnf install curl wget nano unzip git -y
Terminal showing DNF/YUM package management commands and output.
Terminal showing DNF/YUM package management commands and output.

Step 4: Install and Configure PostgreSQL 16

First, enable the PostgreSQL repository:

dnf install https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm -y
Terminal showing DNF/YUM package management commands and output.

Install PostgreSQL 16:

dnf install postgresql16 postgresql16-server postgresql16-contrib -y
Terminal showing DNF/YUM package management commands and output.

Initialize the database:

/usr/pgsql-16/bin/postgresql-16-setup initdb
Terminal showing PostgreSQL configuration commands for Strapi.

Enable and start the service:

systemctl enable postgresql-16
systemctl start postgresql-16
Terminal showing systemd service management commands or output.

Log into PostgreSQL and create the database and user:

su - postgres -c psql

Inside the prompt:

CREATE DATABASE strapidb;
CREATE USER strapiuser WITH ENCRYPTED PASSWORD 'YourStrongPassword';
ALTER DATABASE strapidb OWNER TO strapiuser;
\q
Terminal showing SQL database and user configuration commands.

Step 5: Install Node.js 20 and NPM

curl -fsSL https://rpm.nodesource.com/setup_20.x | bash -
dnf install nodejs -y
node -v
npm -v
Terminal showing DNF/YUM package management commands and output.
Terminal showing DNF/YUM package management commands and output.
Terminal showing Node.js or npm version output.

Step 6: Create and Build Strapi Project

Navigate to the root directory and create a new Strapi app:

cd /root
npm create strapi-app@latest my-project
Terminal showing npm commands or output during Create and Build Strapi Project.

Go to the project folder and build for production:

cd my-project
NODE_ENV=production npm run build
Terminal showing file and directory setup commands.

Install PostgreSQL support:

npm install pg
Terminal showing npm package installation messages.

Step 7: Install and Configure PM2

Install PM2 globally:

npm install -g pm2
Terminal showing PM2 application process management output.

Create the PM2 configuration:

nano ecosystem.config.js

Paste:

module.exports = {
  apps: [
    {
      name: 'strapi',
      cwd: '/root/my-project',
      script: 'npm',
      args: 'start',
      env: {
        NODE_ENV: 'production',
        HOST: '127.0.0.1',
        PORT: '1337',
        DATABASE_CLIENT: 'postgres',
        DATABASE_HOST: '127.0.0.1',
        DATABASE_PORT: '5432',
        DATABASE_NAME: 'strapidb',
        DATABASE_USERNAME: 'strapiuser',
        DATABASE_PASSWORD: 'YourStrongPassword'
      }
    }
  ]
};
Terminal showing the GNU nano editor and configuration text.

Start and enable PM2:

pm2 start ecosystem.config.js
pm2 startup
pm2 save
Terminal showing PM2 application process management output.
Terminal showing systemd service management commands or output.
Terminal showing PM2 application process management output.

Step 8: Install and Configure Nginx

dnf install nginx -y
Terminal showing DNF/YUM package management commands and output.

Create the reverse proxy configuration:

nano /etc/nginx/conf.d/strapi.conf

Paste:

server {
    listen 80;
    server_name your_domain.com;

    location / {
        proxy_pass http://127.0.0.1:1337;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_cache_bypass $http_upgrade;
    }
}
Terminal showing web server configuration text.

Enable and restart Nginx:

systemctl enable nginx
systemctl restart nginx
Terminal showing systemd service management commands or output.

Step 9: Install and Configure SSL with Let’s Encrypt

dnf install certbot python3-certbot-nginx -y
certbot --nginx -d almalinux-tutorials.shape.host -m contact@shape.host --agree-tos --no-eff-email
Terminal showing DNF/YUM package management commands and output.
Terminal showing Certbot certificate commands and messages.

Strapi CMS is now installed and running on AlmaLinux 9 with PostgreSQL, PM2, Nginx, and SSL.

You can access it at:

https://almalinux-tutorials.shape.host
Strapi initial administrator registration form.
Strapi administrator welcome dashboard with project tools.

Use Shape.Host for fast and secure Cloud VPS hosting.