Strapi Production Setup — PostgreSQL, NODE_ENV, and Process Management
In this tutorial, you will learn how to configure Strapi for production deployment — switching to PostgreSQL, setting up environment configurations, managing the Node.js process with PM2, and configuring the server for production workloads.
What You'll Learn
- How to configure PostgreSQL for Strapi production
- How NODE_ENV affects Strapi behavior
- How to manage Strapi processes with PM2
- How to configure the production server settings
- How to build the admin panel for production
- Production checklist and common pitfalls
Why It Matters
Running Strapi in production is fundamentally different from development. You must use a proper database, configure the server for security and performance, manage the process to handle crashes, and ensure the admin panel is built for production. Getting these wrong leads to downtime, data loss, and security vulnerabilities.
Real-World Use
A content website running Strapi serves 500,000 API requests per day. The production setup includes PostgreSQL on a dedicated database server, 4 Strapi processes managed by PM2 behind an nginx reverse proxy, Redis caching, and Cloudflare CDN. When one Strapi process crashes, PM2 restarts it automatically. When traffic spikes, the system handles it without downtime.
Learning Path
flowchart LR A["Testing"] --> B["Production Setup
-- You are here"]:::current B --> C["Database Configuration"] C --> D["Environment Variables"] D --> E["CI/CD"] classDef current fill:#4945ff,color:#fff,stroke-width:2px
PostgreSQL Configuration
PostgreSQL is the recommended production database for Strapi.
# Install PostgreSQL (Ubuntu/Debian)
sudo apt update
sudo apt install postgresql postgresql-contrib
# Create a database and user for Strapi
sudo -u postgres psql
CREATE DATABASE strapi_production;
CREATE USER strapi_user WITH ENCRYPTED PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE strapi_production TO strapi_user;
\q
Configure Strapi to use PostgreSQL:
// config/database.js
module.exports = ({ env }) => ({
connection: {
client: "postgres",
connection: {
host: env("DATABASE_HOST", "localhost"),
port: env.int("DATABASE_PORT", 5432),
database: env("DATABASE_NAME", "strapi_production"),
user: env("DATABASE_USERNAME", "strapi_user"),
password: env("DATABASE_PASSWORD", "secure_password"),
ssl: env.bool("DATABASE_SSL", true),
schema: "public",
},
pool: {
min: 2,
max: 10,
acquireTimeoutMillis: 30000,
createTimeoutMillis: 30000,
idleTimeoutMillis: 30000,
reapIntervalMillis: 1000,
},
debug: false,
},
});
The pool settings control database connection management. For most production deployments:
min: 2— Keep at least 2 connections readymax: 10— Maximum 10 concurrent connections- Adjust
maxbased on your server's memory and expected traffic
NODE_ENV Configuration
The NODE_ENV environment variable changes how Strapi behaves:
// NODE_ENV=production causes:
// 1. Admin panel served from built files (not development server)
// 2. Error messages are less verbose (no stack traces)
// 3. Logger outputs JSON format (for log aggregation)
// 4. Performance optimizations enabled
// Set in .env.production:
NODE_ENV=production
// config/server.js — Production server configuration
module.exports = ({ env }) => ({
host: env("HOST", "0.0.0.0"),
port: env.int("PORT", 1337),
app: {
keys: env.array("APP_KEYS"),
},
admin: {
auth: {
secret: env("ADMIN_JWT_SECRET"),
},
// In production, disable auto-open
autoOpen: false,
// Custom admin URL path
url: env("ADMIN_URL", "/admin"),
// Serve admin panel from built files
serveAdminPanel: true,
},
// URL that the API is accessible from
url: env("PUBLIC_URL", "https://api.example.com"),
// Proxy support (if behind nginx or Cloudflare)
proxy: env.bool("IS_PROXY", true),
// Cron jobs configuration
cron: {
enabled: env.bool("CRON_ENABLED", false),
},
});
Building for Production
Before starting in production, build the admin panel:
# Build the admin panel (compiles React app to static files)
NODE_ENV=production npm run build
# Output:
# ✔ Building admin panel...
# ✔ Admin panel built successfully
# The admin panel is served at /admin
# Start the production server
NODE_ENV=production npm run start
The build process compiles the React admin panel into optimized static files. Without this step, the admin panel will not work in production.
Process Management with PM2
PM2 keeps your Strapi process running and restarts it if it crashes.
# Install PM2 globally
npm install -g pm2
# Start Strapi with PM2
pm2 start npm --name "strapi" -- run start
# With ecosystem.config.js
// ecosystem.config.js
module.exports = {
apps: [
{
name: "strapi",
script: "npm",
args: "start",
env: {
NODE_ENV: "production",
PORT: 1337,
},
// Cluster mode (run multiple instances)
exec_mode: "cluster",
instances: 4, // Number of CPU cores
// Memory limit — restart if exceeds 500MB
max_memory_restart: "500M",
// Logging
error_file: "./logs/strapi-error.log",
out_file: "./logs/strapi-out.log",
merge_logs: true,
log_date_format: "YYYY-MM-DD HH:mm:ss",
// Auto-restart on crash
autorestart: true,
// Watch for file changes (disable in production)
watch: false,
// Max restarts within 10 seconds
max_restarts: 10,
restart_delay: 4000,
// Graceful shutdown
kill_timeout: 5000,
// Environment variables file
env_file: ".env.production",
},
],
};
Start with PM2 configuration:
# Start with ecosystem file
pm2 start ecosystem.config.js
# Save the PM2 process list (survives reboot)
pm2 save
# Auto-start PM2 on server reboot
pm2 startup
# Monitor processes
pm2 monit
# View logs
pm2 logs strapi
# Restart
pm2 restart strapi
# Stop
pm2 stop strapi
Reverse Proxy Configuration
Run Strapi behind nginx for SSL termination, static file serving, and Load Balancing:
# /etc/nginx/sites-available/strapi
server {
listen 80;
server_name api.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Max upload size
client_max_body_size 10M;
# Strapi API
location /api/ {
proxy_pass http://127.0.0.1:1337;
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_cache_bypass $http_upgrade;
}
# Admin panel
location /admin {
proxy_pass http://127.0.0.1:1337;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# Uploads (serve directly with caching)
location /uploads/ {
proxy_pass http://127.0.0.1:1337;
proxy_cache STATIC;
proxy_cache_valid 200 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
}
}
Production Checklist
[ ] PostgreSQL configured and connected
[ ] JWT secrets set in environment variables
[ ] Admin panel built with NODE_ENV=production
[ ] PM2 configured for process management
[ ] Reverse proxy (nginx) configured
[ ] SSL certificates installed
[ ] Database backups configured
[ ] Logging configured (logs rotated)
[ ] Monitoring set up (health checks)
[ ] Firewall configured (only ports 80, 443 open)
[ ] Rate limiting enabled
[ ] CORS configured for frontend domains
[ ] Upload provider configured (S3/Cloudinary)
[ ] Redis configured for caching (optional)
Common Mistakes
Using SQLite in production. SQLite fails under concurrent writes. Always use PostgreSQL or MySQL for production. Migrate your data before going live.
Not building the admin panel. The admin panel development server is not suitable for production. Run
NODE_ENV=production npm run buildbefore starting.Running a single process with no process manager. If the Node.js process crashes, Strapi is down until someone restarts it. Use PM2 to auto-restart.
Exposing Strapi directly to the internet without a reverse proxy. A reverse proxy handles SSL, request buffering, static file caching, and protects against certain attacks. Always use nginx or similar.
Not setting APP_KEYS and JWT secrets. Missing app keys cause session errors. Missing JWT secrets break authentication. These must be set in environment variables.
Practice Questions
What database should you use for Strapi production and why? Answer: PostgreSQL. It handles concurrent writes, provides ACID Compliance, supports JSONB for flexible queries, and has good performance at scale.
What does NODE_ENV=production change in Strapi? Answer: It serves the built admin panel (not the dev server), reduces error verbosity, enables performance optimizations, and changes logger format to JSON.
Why should you use PM2 for production Strapi? Answer: PM2 provides automatic restart on crash, cluster mode for multi-core usage, log management, graceful shutdown, and process monitoring.
Challenge: Set up a complete production Strapi deployment: (1) Install and configure PostgreSQL with a dedicated database and user, (2) Configure
config/database.jsfor PostgreSQL with connection pooling, (3) Set environment variables for JWT secrets, app keys, and database credentials, (4) Build the admin panel for production, (5) Install and configure PM2 with 4 cluster instances, (6) Set up nginx as a reverse proxy with SSL, (7) Test that the production setup works by making API requests and accessing the admin panel, (8) Write a health check endpoint and test PM2 auto-restart by killing the process.
FAQ
Mini Project
Your task: Deploy Strapi to a production-like environment.
- Set up a Linux server (local VM or cloud instance).
- Install Node.js 20, PostgreSQL, nginx, and PM2.
- Clone your Strapi project to the server.
- Configure PostgreSQL database and user.
- Configure all environment variables for production.
- Build the admin panel.
- Set up PM2 with cluster mode (2 instances).
- Configure nginx as a reverse proxy with a self-signed SSL certificate (for testing).
- Test API endpoints and admin panel access.
- Configure log rotation and monitoring.
- Document the complete deployment process.
What's Next
Now that you have a production-ready Strapi setup, proceed to Database Configuration to learn about PostgreSQL vs MySQL, database migrations, and connection optimization. After that, explore Environment Variables for managing configuration across environments.
Related lessons:
- Node.js Production — Node.js production best practices
- PostgreSQL — Database administration
- REST API — Production API considerations
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro