Strapi Headless CMS Installation

Strapi is an open-source headless CMS providing flexible content management with REST and GraphQL APIs, role-based access control, and extensible plugin system. Installing Strapi enables rapid content API development without frontend coupling, allowing content delivery to websites, mobile apps, and other channels. This guide covers production-ready Strapi deployment including Node.js setup, Strapi installation, PostgreSQL database, Nginx reverse proxy, PM2 process management, content type configuration, user roles, storage configuration, and production optimization.

Table of Contents

Strapi Architecture Overview

Strapi provides a content management backend with REST/GraphQL APIs, admin interface for content creation, and plugin system for extensibility.

Architecture components:

  • Admin Panel: content creation and configuration interface
  • REST API: JSON endpoint for content retrieval
  • GraphQL API: flexible query language for content
  • Database: stores content and configuration
  • Plugins: extend functionality with packages
  • Roles & Permissions: fine-grained access control
  • Media Library: asset management and delivery

Request flow:

  1. Admin creates content via web interface
  2. Content stored in PostgreSQL database
  3. Frontend/mobile app requests via REST/GraphQL
  4. API returns content in JSON format
  5. Frontend renders content

Node.js Environment

Set up Node.js runtime for Strapi.

Update system:

sudo apt update
sudo apt upgrade -y
sudo apt install curl wget git zip unzip vim htop build-essential -y

Install Node.js 18 LTS:

# Add NodeSource repository
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -

# Install Node.js
sudo apt install nodejs -y

# Verify
node --version
npm --version

Create application user:

sudo useradd -m -s /bin/bash strapi
sudo usermod -aG www-data strapi

# Create application directory
sudo mkdir -p /home/strapi/app
sudo chown -R strapi:www-data /home/strapi/app

Install PM2 globally:

sudo npm install -g pm2 yarn

# Verify
pm2 --version
yarn --version

Install Nginx:

sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx

Install PostgreSQL:

sudo apt install postgresql postgresql-contrib libpq-dev -y
sudo systemctl start postgresql
sudo systemctl enable postgresql

PostgreSQL Setup

Create database and user for Strapi.

Connect to PostgreSQL:

sudo -u postgres psql

Create database and user:

CREATE DATABASE strapi_db;
CREATE USER strapi_user WITH PASSWORD 'SecurePassword123!';
ALTER ROLE strapi_user SET client_encoding TO 'utf8';
ALTER ROLE strapi_user SET default_transaction_isolation TO 'read committed';
ALTER ROLE strapi_user SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE strapi_db TO strapi_user;
\q

Verify connection:

psql -U strapi_user -d strapi_db -h localhost
# Should connect successfully
\q

Strapi Installation

Install and initialize Strapi application.

Create Strapi project:

cd /home/strapi/app

# Use Strapi CLI to create new project
sudo -u strapi npx create-strapi-app@latest . --database=postgres

# When prompted, provide database info:
# Database client: PostgreSQL
# Database host: localhost
# Database port: 5432
# Database name: strapi_db
# Database username: strapi_user
# Database password: SecurePassword123!
# Enable SSL: false

Or clone existing Strapi project:

cd /home/strapi/app
sudo -u strapi git clone https://github.com/yourname/strapi-cms.git .

Install dependencies:

cd /home/strapi/app
sudo -u strapi npm install

# Or with yarn
sudo -u strapi yarn install

Create .env file:

sudo -u strapi cat > /home/strapi/app/.env << 'EOF'
NODE_ENV=production
ADMIN_JWT_SECRET=your-secret-key-here
API_TOKEN_SALT=your-salt-here
DATABASE_CLIENT=postgres
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=strapi_db
DATABASE_USERNAME=strapi_user
DATABASE_PASSWORD=SecurePassword123!
DATABASE_SSL=false
JWT_SECRET=your-jwt-secret-here
TRANSFER_TOKEN_SALT=your-transfer-salt-here
EOF

Generate secret keys:

# Generate for ADMIN_JWT_SECRET
openssl rand -base64 32

# Generate for other secrets
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"

Update .env with generated secrets:

sudo -u strapi nano /home/strapi/app/.env

# Paste generated secrets

Build Strapi for production:

cd /home/strapi/app

# Build admin panel
sudo -u strapi npm run build

# Verify build
ls -la build/

Application Configuration

Configure Strapi settings for production.

Create server configuration:

sudo -u strapi cat > /home/strapi/app/config/server.js << 'EOF'
module.exports = ({ env }) => ({
  host: env('HOST', '127.0.0.1'),
  port: env.int('PORT', 1337),
  app: {
    keys: env.array('APP_KEYS'),
  },
  webhooks: {
    events: {
      EE: false,
      isEnabled: true,
    },
  },
});
EOF

Configure database settings:

sudo -u strapi cat > /home/strapi/app/config/database.js << 'EOF'
module.exports = ({ env }) => ({
  connection: {
    client: 'postgres',
    connection: {
      host: env('DATABASE_HOST', 'localhost'),
      port: env.int('DATABASE_PORT', 5432),
      database: env('DATABASE_NAME', 'strapi_db'),
      user: env('DATABASE_USERNAME', 'strapi_user'),
      password: env('DATABASE_PASSWORD', 'SecurePassword123!'),
      ssl: env.bool('DATABASE_SSL', false),
    },
    pool: { min: 2, max: 10 },
    debug: false,
  },
});
EOF

Configure admin interface:

sudo -u strapi cat > /home/strapi/app/config/admin.js << 'EOF'
module.exports = ({ env }) => ({
  auth: {
    secret: env('ADMIN_JWT_SECRET'),
  },
  apiToken: {
    salt: env('API_TOKEN_SALT'),
  },
  url: env('ADMIN_URL', '/admin'),
});
EOF

Test configuration:

cd /home/strapi/app

# Run Strapi (will start on port 1337)
sudo -u strapi npm run start

# Press Ctrl+C to stop
# Admin should be available at http://localhost:1337/admin

Content Types and API

Create content types and configure API access.

Create content type via admin interface:

# Access admin at http://localhost:1337/admin
# Navigate to Content-Type Builder
# Click "Create new collection type"
# Define fields:
# - title (String, Required)
# - description (Text)
# - slug (UID, from title)
# - content (Rich Text)
# - published (Boolean)
# - author (Relation to User)

Or create via code:

sudo -u strapi cat > /home/strapi/app/src/api/article/content-types/article/schema.json << 'EOF'
{
  "kind": "collectionType",
  "collectionName": "articles",
  "info": {
    "singularName": "article",
    "pluralName": "articles"
  },
  "attributes": {
    "title": {
      "type": "string",
      "required": true
    },
    "description": {
      "type": "text"
    },
    "content": {
      "type": "richtext"
    },
    "slug": {
      "type": "uid",
      "targetField": "title"
    },
    "published": {
      "type": "boolean",
      "default": false
    }
  }
}
EOF

Configure API endpoints:

# REST endpoint: /api/articles
# GraphQL query example:
# query {
#   articles {
#     data {
#       id
#       attributes {
#         title
#         content
#       }
#     }
#   }
# }

Enable GraphQL:

# In Strapi admin:
# Navigate to Plugins > GraphQL
# Click "GraphQL Playground" to test queries

Users and Permissions

Configure role-based access control.

Create admin user:

# Via admin interface:
# Navigate to Settings > Users
# Click "Invite new user"
# Email: [email protected]
# Role: Super Admin

Or create via CLI:

cd /home/strapi/app
sudo -u strapi npm run strapi admin:create-user -- \
  --firstname Admin \
  --lastname User \
  --email [email protected] \
  --password AdminPassword123!

Create custom roles:

# Via admin interface:
# Navigate to Settings > Roles
# Click "New role"
# Name: Author
# Set permissions for content management

# Permissions:
# - Articles: Create, Read, Update, Delete (own)
# - Media: Upload, View

Create API tokens:

# Via admin interface:
# Navigate to Settings > API Tokens
# Click "Create new API token"
# Name: Frontend API Token
# Description: For frontend application
# Select permissions:
# - articles: find, findOne
# - media: read

Media Library and Storage

Configure media management and storage.

Upload and manage media:

# Via admin interface:
# Navigate to Media Library
# Upload images and files
# Default: stored in /public/uploads/

# Access via API:
# /api/upload/files

Configure local file storage:

sudo -u strapi cat > /home/strapi/app/config/middlewares.js << 'EOF'
module.exports = [
  'strapi::errors',
  'strapi::security',
  'strapi::cors',
  'strapi::poweredBy',
  'strapi::logger',
  'strapi::query',
  'strapi::body',
  'strapi::session',
  'strapi::favicon',
  'strapi::public',
];
EOF

Or configure cloud storage (AWS S3):

# Install AWS S3 plugin
sudo -u strapi npm install @strapi/provider-upload-aws-s3

# Configure in config/plugins.js
cat > /home/strapi/app/config/plugins.js << 'EOF'
module.exports = {
  upload: {
    config: {
      provider: 'aws-s3',
      providerOptions: {
        s3Options: {
          accessKeyId: process.env.AWS_ACCESS_KEY_ID,
          secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
          region: process.env.AWS_REGION,
          params: {
            Bucket: process.env.AWS_BUCKET,
          },
        },
      },
      actionOptions: {
        upload: {},
        uploadStream: {},
      },
    },
  },
};
EOF

Nginx Reverse Proxy

Configure Nginx to proxy requests to Strapi.

Create Nginx configuration:

sudo cat > /etc/nginx/sites-available/strapi.conf << 'EOF'
upstream strapi {
    server 127.0.0.1:1337;
    keepalive 64;
}

server {
    listen 80;
    server_name cms.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name cms.example.com;

    ssl_certificate /etc/letsencrypt/live/cms.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cms.example.com/privkey.pem;

    access_log /var/log/nginx/strapi_access.log;
    error_log /var/log/nginx/strapi_error.log;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;

    # Gzip compression
    gzip on;
    gzip_types application/json text/plain text/css text/javascript;

    # Cache uploads
    location /uploads/ {
        expires 30d;
        add_header Cache-Control "public, max-age=2592000";
        proxy_pass http://strapi;
    }

    # Proxy to Strapi
    location / {
        proxy_pass http://strapi;
        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_http_version 1.1;
        proxy_set_header Connection "upgrade";
        proxy_set_header Upgrade $http_upgrade;
    }
}
EOF

sudo ln -s /etc/nginx/sites-available/strapi.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

PM2 Process Management

Configure PM2 to manage Strapi process.

Create PM2 configuration:

sudo -u strapi cat > /home/strapi/app/ecosystem.config.js << 'EOF'
module.exports = {
  apps: [
    {
      name: 'strapi-cms',
      script: 'npm',
      args: 'start',
      instances: 1,
      exec_mode: 'fork',
      env: {
        NODE_ENV: 'production',
        PORT: 1337,
      },
      error_file: '/home/strapi/app/logs/pm2-error.log',
      out_file: '/home/strapi/app/logs/pm2-out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      max_memory_restart: '500M',
      restart_delay: 4000,
      min_uptime: '10s',
      max_restarts: 10,
    }
  ]
}
EOF

Create logs directory:

sudo -u strapi mkdir -p /home/strapi/app/logs

Start with PM2:

cd /home/strapi/app

# Start process
sudo -u strapi pm2 start ecosystem.config.js

# Save process list
sudo -u strapi pm2 save

# Create systemd service
sudo pm2 startup systemd -u strapi --hp /home/strapi

Create systemd service:

sudo systemctl enable pm2-strapi
sudo systemctl status pm2-strapi

Monitor Strapi:

# Check status
pm2 list

# View logs
pm2 logs strapi-cms

# Monitor
pm2 monit

Conclusion

Installing and configuring Strapi provides a flexible headless CMS enabling content management with REST and GraphQL APIs. This guide covers production-ready deployment with PostgreSQL database, Nginx reverse proxy, PM2 process management, content type configuration, and role-based access control. Key focus areas are proper environment configuration for production, database setup for data persistence, PM2 service management for reliability, Nginx reverse proxy for request handling, and role-based permissions for content team access. Regular backups of database and media files ensure data protection. Following these practices creates a robust Strapi installation ready to power multiple frontend applications and channels with managed content.