{"id":1813,"date":"2026-09-04T11:51:30","date_gmt":"2026-09-04T06:21:30","guid":{"rendered":"https:\/\/cpanelfree.com\/blog\/how-to-host-strapi-headless-cms-linux-vps-postgresql-pm2\/"},"modified":"2026-09-04T11:53:55","modified_gmt":"2026-09-04T06:23:55","slug":"how-to-host-strapi-headless-cms-linux-vps-postgresql-pm2","status":"publish","type":"post","link":"https:\/\/cpanelfree.com\/blog\/how-to-host-strapi-headless-cms-linux-vps-postgresql-pm2\/","title":{"rendered":"How to Self-Host Strapi Headless CMS on Linux VPS (PostgreSQL, PM2, and Nginx)"},"content":{"rendered":"<h2>Why Self-Host Strapi Headless CMS on Your Own Infrastructure?<\/h2>\n<p>Headless CMS architectures have become the gold standard for decoupling content management from modern frontend frameworks like Next.js, Nuxt, Astro, and mobile Flutter\/React Native apps. Strapi is the leading open-source Node.js headless CMS, offering an intuitive administrative UI, dynamic content-type builders, automated GraphQL\/REST API generation, and granular role-based permissions. While managed SaaS platforms charge hundreds of dollars per month with strict record limits, self-hosting Strapi on a cloud Linux VPS gives you unlimited content types, full database ownership, custom plugins, and complete privacy at zero recurring software costs.<\/p>\n<p>In this end-to-end production deployment guide, we will configure Strapi on Ubuntu 24.04\/22.04 LTS backed by enterprise PostgreSQL, managed by PM2 process manager for continuous background execution, secured behind an Nginx reverse proxy, and encrypted with Let&#8217;s Encrypt SSL.<\/p>\n<h2>Step 1: Installing Node.js LTS Runtimes and Build Tooling<\/h2>\n<p>Strapi requires active Node.js LTS versions (Node.js 18 or 20) along with essential native C++ build tools like <code>gcc<\/code>, <code>g++<\/code>, and <code>make<\/code> to compile database bindings:<\/p>\n<pre><code># Install Node.js 20 LTS via official NodeSource repository\ncurl -fsSL https:\/\/deb.nodesource.com\/setup_20.x | sudo -E bash -\nsudo apt update &amp;&amp; sudo apt install -y nodejs build-essential git\n\n# Verify Node and NPM runtime versions\nnode -v\nnpm -v\n\n# Install PM2 process manager globally\nsudo npm install -g pm2<\/code><\/pre>\n<h2>Step 2: Installing and Hardening PostgreSQL Database<\/h2>\n<p>Production Strapi deployments should always utilize PostgreSQL instead of lightweight SQLite for superior concurrency, ACID transaction guarantees, and reliable automated backups:<\/p>\n<pre><code># Install PostgreSQL server and client libraries\nsudo apt install -y postgresql postgresql-contrib\n\n# Switch to postgres user and create dedicated database &amp; user\nsudo -u postgres psql &lt;&lt; 'EOF'\nCREATE DATABASE strapi_production;\nCREATE USER strapi_admin WITH ENCRYPTED PASSWORD 'UltraSecureDbPass2026!';\nGRANT ALL PRIVILEGES ON DATABASE strapi_production TO strapi_admin;\nALTER DATABASE strapi_production OWNER TO strapi_admin;\n\\q\nEOF<\/code><\/pre>\n<h2>Step 3: Initializing and Building the Strapi Project<\/h2>\n<p>Create your Strapi application in a standardized production directory like <code>\/var\/www\/strapi-app<\/code>:<\/p>\n<pre><code>cd \/var\/www\n# Initialize Strapi with no quickstart template to connect PostgreSQL\nnpx create-strapi-app@latest strapi-app --no-run\n\n# Navigate into project directory\ncd \/var\/www\/strapi-app<\/code><\/pre>\n<p>Create a production environment file at <code>\/var\/www\/strapi-app\/.env<\/code> with cryptographically secure random keys:<\/p>\n<pre><code>HOST=127.0.0.1\nPORT=1337\n\n# App security keys\nAPP_KEYS=generate_random_key_1,generate_random_key_2\nAPI_TOKEN_SALT=generate_random_salt_1\nADMIN_JWT_SECRET=generate_random_admin_secret\nTRANSFER_TOKEN_SALT=generate_random_transfer_salt\nJWT_SECRET=generate_random_jwt_secret\n\n# Database Configuration\nDATABASE_CLIENT=postgres\nDATABASE_HOST=127.0.0.1\nDATABASE_PORT=5432\nDATABASE_NAME=strapi_production\nDATABASE_USERNAME=strapi_admin\nDATABASE_PASSWORD=UltraSecureDbPass2026!\nDATABASE_SSL=false<\/code><\/pre>\n<p>Build the production admin panel bundle:<\/p>\n<pre><code>NODE_ENV=production npm run build<\/code><\/pre>\n<h2>Step 4: Configuring PM2 Cluster Daemon and Ecosystem<\/h2>\n<p>To ensure Strapi starts automatically upon server reboot and restarts automatically if memory thresholds are exceeded, create a PM2 ecosystem file <code>\/var\/www\/strapi-app\/ecosystem.config.js<\/code>:<\/p>\n<pre><code>module.exports = {\n  apps: [\n    {\n      name: 'strapi-cms',\n      cwd: '\/var\/www\/strapi-app',\n      script: 'npm',\n      args: 'run start',\n      env: {\n        NODE_ENV: 'production',\n      },\n      instances: 1,\n      autorestart: true,\n      max_memory_restart: '800M',\n      error_file: '\/var\/log\/strapi-error.log',\n      out_file: '\/var\/log\/strapi-out.log',\n    },\n  ],\n};<\/code><\/pre>\n<p>Start the daemon and generate the systemd startup hook:<\/p>\n<pre><code>pm2 start ecosystem.config.js\npm2 save\npm2 startup systemd<\/code><\/pre>\n<h2>Step 5: Nginx Reverse Proxy with WebSocket &amp; File Upload Buffers<\/h2>\n<p>Strapi requires handling large media library uploads and real-time dashboard updates. Configure <code>\/etc\/nginx\/sites-available\/cms.example.com<\/code>:<\/p>\n<pre><code>server {\n    listen 80;\n    server_name cms.example.com;\n\n    client_max_body_size 50M;\n\n    location \/ {\n        proxy_pass http:\/\/127.0.0.1:1337;\n        proxy_http_version 1.1;\n        proxy_set_header X-Forwarded-Host $host;\n        proxy_set_header X-Forwarded-Server $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_set_header Host $http_host;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection \"Upgrade\";\n    }\n}<\/code><\/pre>\n<p>Enable the virtual host and acquire a free Let&#8217;s Encrypt SSL certificate:<\/p>\n<pre><code>sudo ln -s \/etc\/nginx\/sites-available\/cms.example.com \/etc\/nginx\/sites-enabled\/\nsudo nginx -t &amp;&amp; sudo systemctl reload nginx\nsudo certbot --nginx -d cms.example.com<\/code><\/pre>\n<h2>Production Hardening &amp; Performance Checklist<\/h2>\n<ul>\n<li><strong>Offload Media Uploads to S3\/R2:<\/strong> Install <code>@strapi\/provider-upload-aws-s3<\/code> to store media library images on cloud object storage rather than filling local VPS storage.<\/li>\n<li><strong>Enable Response Caching:<\/strong> Use Strapi REST cache plugins or Cloudflare Edge caching to serve static API responses in sub-20ms.<\/li>\n<li><strong>Setup Automated PostgreSQL Backups:<\/strong> Schedule daily <code>pg_dump<\/code> cron jobs encrypted and synchronized to offsite storage.<\/li>\n<\/ul>\n<h2>Automating Production Media Backups with S3-Compatible Object Storage<\/h2>\n<p>When running a headless CMS in production, storing user-uploaded media files (high-resolution images, PDF documents, video clips) directly on the local VPS SSD can quickly exhaust local disk capacity and complicate disaster recovery. The recommended industry practice is to offload media storage to S3-compatible cloud object storage (such as Cloudflare R2, AWS S3, or Backblaze B2).<\/p>\n<p>Install the official S3 upload provider plugin inside your Strapi application directory:<\/p>\n<pre><code># Install Strapi S3 provider\nnpm install @strapi\/provider-upload-aws-s3 --save<\/code><\/pre>\n<p>Configure the provider in <code>\/var\/www\/strapi-app\/config\/plugins.js<\/code>:<\/p>\n<pre><code>module.exports = ({ env }) =&gt; ({\n  upload: {\n    config: {\n      provider: 'aws-s3',\n      providerOptions: {\n        s3Options: {\n          accessKeyId: env('AWS_ACCESS_KEY_ID'),\n          secretAccessKey: env('AWS_ACCESS_SECRET'),\n          region: env('AWS_REGION'),\n          endpoint: env('AWS_ENDPOINT'),\n          params: {\n            Bucket: env('AWS_BUCKET'),\n          },\n        },\n      },\n      actionOptions: {\n        upload: {},\n        uploadStream: {},\n        delete: {},\n      },\n    },\n  },\n});<\/code><\/pre>\n<h2>Securing Strapi Headless CMS with Web Application Firewalls<\/h2>\n<p>Exposing an administrative CMS to the public internet requires robust defenses against credential stuffing and brute-force attacks. Implement rate limiting and security headers in your Nginx reverse proxy configuration:<\/p>\n<pre><code># Rate limit zone: 10 requests per second per IP\nlimit_req_zone $binary_remote_addr zone=strapi_limit:10m rate=10r\/s;\n\nserver {\n    server_name cms.example.com;\n\n    # Apply rate limiting to admin login endpoints\n    location \/admin {\n        limit_req zone=strapi_limit burst=15 nodelay;\n        proxy_pass http:\/\/127.0.0.1:1337;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n    }\n}<\/code><\/pre>\n<div style=\"background-color: #0f172a;border-left: 4px solid #38bdf8;padding: 18px 24px;margin: 30px 0;border-radius: 8px\">\n<h3 style=\"color: #38bdf8;margin-top: 0\">Recommended Related Technical Guides<\/h3>\n<ul style=\"margin-bottom: 0;color: #cbd5e1\">\n<li><a href=\"https:\/\/cpanelfree.com\/blog\/how-to-deploy-nodejs-express-app-linux-vps-nginx-pm2\/\" style=\"color: #38bdf8;text-decoration: underline\">Deploying Node.js &amp; Express Apps with PM2 and Nginx<\/a><\/li>\n<li><a href=\"https:\/\/cpanelfree.com\/blog\/how-to-run-docker-docker-compose-cheap-linux-vps\/\" style=\"color: #38bdf8;text-decoration: underline\">Running Docker and Docker Compose on Cheap Linux VPS<\/a><\/li>\n<li><a href=\"https:\/\/cpanelfree.com\/blog\/how-to-setup-free-ssl-certificate-lets-encrypt-certbot-apache-nginx\/\" style=\"color: #38bdf8;text-decoration: underline\">Setting Up Free Let&#8217;s Encrypt SSL Certificates with Certbot<\/a><\/li>\n<\/ul>\n<\/div>\n<div style=\"background: linear-gradient(135deg, #0284c7 0%, #0369a1 100%);color: #ffffff;padding: 28px;border-radius: 12px;margin: 35px 0;text-align: center\">\n<h3 style=\"color: #ffffff;margin-top: 0;font-size: 22px\">Host Strapi &amp; Node.js on Fast CpanelFree Cloud Servers<\/h3>\n<p style=\"color: #e0f2fe;font-size: 15px;max-width: 650px;margin: 0 auto 18px auto\">Get dedicated RAM, lightning-fast NVMe storage, and 99.9% uptime for your headless CMS and API backends with 100% free hosting and VPS options.<\/p>\n<p>  <a href=\"https:\/\/cpanelfree.com\/\" style=\"background-color: #ffffff;color: #0284c7;font-weight: 700;padding: 12px 28px;border-radius: 8px;text-decoration: none;display: inline-block\">Get Free Cloud Hosting Today &rarr;<\/a>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Why Self-Host Strapi Headless CMS on Your Own Infrastructure? Headless CMS architectures have become the gold standard for decoupling content management from modern frontend frameworks like Next.js, Nuxt, Astro, and mobile Flutter\/React Native apps. Strapi is the leading open-source Node.js headless CMS, offering an intuitive administrative UI, dynamic content-type builders, automated GraphQL\/REST API generation, and [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1812,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[51],"tags":[],"class_list":["post-1813","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials"],"_links":{"self":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/1813","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/comments?post=1813"}],"version-history":[{"count":2,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/1813\/revisions"}],"predecessor-version":[{"id":1832,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/1813\/revisions\/1832"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media\/1812"}],"wp:attachment":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media?parent=1813"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/categories?post=1813"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/tags?post=1813"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}