Skip to content

Strapi Production Setup — PostgreSQL, NODE_ENV, and Process Management

DodaTech Updated 2026-06-28 8 min read

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 ready
  • max: 10 — Maximum 10 concurrent connections
  • Adjust max based 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

  1. Using SQLite in production. SQLite fails under concurrent writes. Always use PostgreSQL or MySQL for production. Migrate your data before going live.

  2. Not building the admin panel. The admin panel development server is not suitable for production. Run NODE_ENV=production npm run build before starting.

  3. 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.

  4. 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.

  5. 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

  1. 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.

  2. 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.

  3. 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.

  4. Challenge: Set up a complete production Strapi deployment: (1) Install and configure PostgreSQL with a dedicated database and user, (2) Configure config/database.js for 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

What is the recommended Node.js version for Strapi production?

Strapi 5 requires Node.js 18 or later. Node.js 20 LTS is recommended for production. Use a Node.js version manager (nvm) to ensure the correct version.

How much RAM does Strapi need in production?

A single Strapi process typically uses 200-400MB of RAM. With cluster mode (4 processes), plan for 1-2GB RAM. Monitor actual usage and adjust accordingly.

Can I run Strapi on a shared hosting plan?

Strapi requires Node.js which is not available on most shared hosting plans. You need a VPS, dedicated server, or a platform like Railway, DigitalOcean App Platform, or Strapi Cloud.

How do I migrate from SQLite to PostgreSQL?

Export your SQLite data as JSON, set up PostgreSQL, import the data using a custom script or the Strapi transfer feature. Test thoroughly before switching production.

What is the difference between npm run develop and npm run start?

npm run develop starts Strapi in development mode with auto-reload and verbose logging. npm run start starts in production mode using the built admin panel. Use start for production.

Mini Project

Your task: Deploy Strapi to a production-like environment.

  1. Set up a Linux server (local VM or cloud instance).
  2. Install Node.js 20, PostgreSQL, nginx, and PM2.
  3. Clone your Strapi project to the server.
  4. Configure PostgreSQL database and user.
  5. Configure all environment variables for production.
  6. Build the admin panel.
  7. Set up PM2 with cluster mode (2 instances).
  8. Configure nginx as a reverse proxy with a self-signed SSL certificate (for testing).
  9. Test API endpoints and admin panel access.
  10. Configure log rotation and monitoring.
  11. 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:

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro