Client Area

Strapi Headless CMS on DomainIndia VPS

ByDomain India Team·DomainIndia Engineering
4 min readPublished 23 Apr 2026Updated 22 Jun 2026152 views

In this article

  • 1Headless vs traditional CMS
  • 2Requirements
  • 3Step 1 — Create Strapi project
  • 4Step 2 — Prepare production config
  • 5Step 3 — Setup VPS

Strapi Headless CMS on DomainIndia VPS

TL;DR
Strapi is the most popular open-source headless CMS — a Node.js admin panel that generates a REST or GraphQL API from content types you define. Perfect for decoupled architectures: Strapi serves content, your frontend (React, Vue, Next.js) consumes it. This guide deploys a production Strapi on DomainIndia VPS.

Headless vs traditional CMS

Traditional (WordPress)Headless (Strapi)
Single repo, HTML+CSS+JSSeparate backend (API) + frontend (any)
Template layer coupled to contentAPI-first, zero presentation
Hard to reuse across web+mobile+IoTOne API serves all clients
PHP ecosystemNode.js ecosystem
Plugins often fragileTypeScript SDK + custom controllers

Pick Strapi when:

  • You need the same content in web + mobile + email
  • You want developers to build custom frontends with React/Vue/Next/Flutter
  • Editorial team wants a friendly UI to manage content
  • You'll outgrow WordPress for content schema complexity

Requirements

  • DomainIndia VPS (2+ GB RAM; 4 GB for production with Postgres)
  • Node.js 20+
  • PostgreSQL (recommended) or MySQL/MariaDB
  • nginx for reverse proxy + SSL

Step 1 — Create Strapi project

On your laptop first (faster dev loop):

bash
npx create-strapi-app@latest my-cms --quickstart
# Quickstart uses SQLite (fine for dev)

Visit http://localhost:1337/admin — create admin account.

Create content types (e.g. "Article" with title, body, author, thumbnail). Strapi auto-generates the API endpoints.

Test API:

bash
curl http://localhost:1337/api/articles

Step 2 — Prepare production config

Edit config/database.ts for production:

typescript
export default ({ env }) => ({
  connection: {
    client: 'postgres',
    connection: {
      host: env('DATABASE_HOST', 'localhost'),
      port: env.int('DATABASE_PORT', 5432),
      database: env('DATABASE_NAME', 'strapi'),
      user: env('DATABASE_USERNAME', 'strapi'),
      password: env('DATABASE_PASSWORD'),
      ssl: env.bool('DATABASE_SSL', false),
    },
    pool: { min: 2, max: 10 },
  },
});

config/server.ts:

typescript
export default ({ env }) => ({
  host: env('HOST', '0.0.0.0'),
  port: env.int('PORT', 1337),
  url: env('PUBLIC_URL', 'https://cms.yourcompany.com'),
  proxy: true,   // behind nginx
  app: {
    keys: env.array('APP_KEYS'),
  },
});

Step 3 — Setup VPS

bash
# On VPS as root
sudo dnf install -y nodejs postgresql-server postgresql-contrib nginx certbot python3-certbot-nginx git
# or Ubuntu:
# curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
# sudo apt install -y nodejs postgresql nginx certbot python3-certbot-nginx git

# PostgreSQL setup
sudo postgresql-setup --initdb
sudo systemctl enable --now postgresql
sudo -u postgres psql <<EOF
CREATE DATABASE strapi;
CREATE USER strapi WITH ENCRYPTED PASSWORD 'changeme';
GRANT ALL PRIVILEGES ON DATABASE strapi TO strapi;
EOF

# Create app user
sudo useradd -m -s /bin/bash strapi
sudo su - strapi

Step 4 — Deploy code

bash
# As strapi user
cd ~
git clone https://github.com/yourcompany/my-cms.git
cd my-cms

npm ci

Create .env:

HOST=0.0.0.0
PORT=1337
NODE_ENV=production
PUBLIC_URL=https://cms.yourcompany.com

DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=strapi
DATABASE_USERNAME=strapi
DATABASE_PASSWORD=changeme
DATABASE_SSL=false

APP_KEYS=key1,key2,key3,key4
API_TOKEN_SALT=random-long-string
ADMIN_JWT_SECRET=another-random-string
TRANSFER_TOKEN_SALT=yet-another
JWT_SECRET=one-more-random-string

Generate random values:

bash
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Build the admin panel:

bash
NODE_ENV=production npm run build

First run to create admin:

bash
npm start
# Visit https://cms.yourcompany.com/admin — create admin account
# Ctrl+C when done

Step 5 — systemd service

/etc/systemd/system/strapi.service:

ini
[Unit]
Description=Strapi CMS
After=network.target postgresql.service

[Service]
Type=simple
User=strapi
Group=strapi
WorkingDirectory=/home/strapi/my-cms
ExecStart=/usr/bin/node /home/strapi/my-cms/node_modules/.bin/strapi start
Restart=on-failure
RestartSec=5

EnvironmentFile=/home/strapi/my-cms/.env

# Hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/home/strapi

StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Start:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now strapi
sudo journalctl -u strapi -f

Step 6 — nginx reverse proxy

/etc/nginx/conf.d/strapi.conf:

nginx
upstream strapi { server 127.0.0.1:1337; }

server {
    listen 80;
    server_name cms.yourcompany.com;

    client_max_body_size 100M;   # for uploads

    location / {
        proxy_pass http://strapi;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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_read_timeout 120s;
    }
}

Test + SSL:

bash
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d cms.yourcompany.com

Step 7 — Frontend integration

Generate API token in Admin → Settings → API Tokens.

Next.js:

typescript
async function getArticles() {
  const res = await fetch(`${process.env.STRAPI_URL}/api/articles?populate=*`, {
    headers: { Authorization: `Bearer ${process.env.STRAPI_TOKEN}` },
    next: { revalidate: 60 },
  });
  const json = await res.json();
  return json.data;
}

Vue/Nuxt:

javascript
const { data } = await useFetch('/api/articles?populate=*', {
  baseURL: 'https://cms.yourcompany.com',
  headers: { Authorization: `Bearer ${config.strapiToken}` },
});

React Native:

Same pattern; use react-query or swr for caching.

Step 8 — Media uploads → S3/R2

By default Strapi stores uploads on local disk — doesn't survive VPS rebuild. Use object storage:

bash
npm install @strapi/provider-upload-aws-s3

config/plugins.ts:

typescript
export default ({ env }) => ({
  upload: {
    config: {
      provider: 'aws-s3',
      providerOptions: {
        credentials: {
          accessKeyId: env('AWS_ACCESS_KEY_ID'),
          secretAccessKey: env('AWS_SECRET_ACCESS_KEY'),
        },
        region: 'auto',
        endpoint: env('AWS_ENDPOINT'),   // e.g. Cloudflare R2 endpoint
        params: { Bucket: env('AWS_BUCKET') },
      },
    },
  },
});

See our Cloudflare R2 article for zero-egress storage setup.

Step 9 — Backups

Daily Postgres + uploads backup:

bash
# /etc/cron.d/strapi-backup
0 3 * * * strapi /bin/bash -c '
  pg_dump -h localhost -U strapi strapi | gzip > /home/strapi/backups/db-$(date +%Y%m%d).sql.gz &&
  tar czf /home/strapi/backups/uploads-$(date +%Y%m%d).tar.gz -C /home/strapi/my-cms/public/uploads . &&
  find /home/strapi/backups -name "*.gz" -mtime +30 -delete
'

See Automated Backups for off-site copies.

Plugins worth installing

  • Internationalization (i18n) — multi-language content
  • Users & Permissions — JWT auth for API
  • Content Versioning — track edits
  • SEO plugin — meta tags per entry
  • GraphQL plugin — GraphQL alongside REST
  • Meilisearch / Algolia plugin — search

Common pitfalls

FAQ

Q Strapi vs Directus vs Payload CMS?

Strapi — largest ecosystem, most plugins. Directus — SQL-first, excellent DB integration. Payload — TypeScript-native, newer but very clean. All valid; Strapi is the safest pick in 2026.

Q Strapi Cloud vs self-hosted?

Strapi Cloud ($15-$300+/mo) — zero ops but US-based. Self-host on DomainIndia VPS — lower cost, data sovereignty in India, more setup work.

Q Scalability?

Strapi handles 1000+ req/sec on a 4 GB VPS with caching. For bigger loads, horizontal scale with load balancer + Redis session/cache.

Q Can I use Strapi for e-commerce?

Possible but not ideal — no built-in cart/checkout. Pair with Medusa.js or use a dedicated e-commerce platform.

Q Is Strapi free?

Community Edition (free, self-host) is feature-complete. Enterprise Edition adds SSO, audit logs, premium support.

Run your own Strapi CMS on a DomainIndia VPS — full control, data in India. Get started

Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket