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
- Node.js Environment
- PostgreSQL Setup
- Strapi Installation
- Application Configuration
- Content Types and API
- Users and Permissions
- Media Library and Storage
- Nginx Reverse Proxy
- PM2 Process Management
- Conclusion
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:
- Admin creates content via web interface
- Content stored in PostgreSQL database
- Frontend/mobile app requests via REST/GraphQL
- API returns content in JSON format
- 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.


