Skip to content

Strapi Environment Variables — .env, Secrets, and Multi-Environment Setup

DodaTech Updated 2026-06-28 9 min read

In this tutorial, you will learn how to manage Strapi environment variables — configuring the .env file system, setting JWT secrets and app keys, managing upload provider credentials, and keeping configuration synchronized across development, staging, and production environments.

What You'll Learn

  • How Strapi loads environment variables and the .env file hierarchy
  • How to configure required environment variables (APP_KEYS, JWT secrets)
  • How to manage database credentials across environments
  • How to set up upload provider credentials securely
  • How to use environment-specific configuration files
  • Best practices for secrets management

Why It Matters

Hardcoding configuration in source code is a security risk and a maintenance nightmare. Database passwords, JWT secrets, and API keys must stay out of version control. Environment variables solve this by keeping configuration separate from code. When you deploy to a new environment, you only change the variables, not the code. Misconfigured environment variables cause cryptic errors that are hard to debug.

Real-World Use

A Strapi team deploys to three environments: development (local SQLite), staging (PostgreSQL on a shared server), and production (PostgreSQL on RDS with SSL). Each environment has different database credentials, different JWT secrets, and different upload provider buckets. With environment variables, the same codebase works in all three environments. Only the .env files differ. When a team member accidentally commits a .env file with production credentials, an automated scan detects it and revokes the exposed keys.

Learning Path

flowchart LR
  A["Database Configuration"] --> B["Environment Variables
-- You are here"]:::current B --> C["CI/CD"] C --> D["Performance"] D --> E["Security & Monitoring"] classDef current fill:#4945ff,color:#fff,stroke-width:2px

How Strapi Loads Environment Variables

Strapi uses dotenv to load environment variables from .env files. The loading order determines which values take precedence:

Loading order (last wins):
1. .env (shared defaults, committed to git)
2. .env.<NODE_ENV> (environment-specific, not committed)
3. Actual process environment variables

Example: If NODE_ENV=production, Strapi loads:
1. .env
2. .env.production (overrides .env values)
3. process.env (overrides both)
# .env — Shared defaults (safe to commit with placeholder values)
HOST=0.0.0.0
PORT=1337
DATABASE_CLIENT=sqlite
DATABASE_FILENAME=.tmp/data.db

# .env.production — Production secrets (DO NOT commit)
NODE_ENV=production
DATABASE_CLIENT=postgres
DATABASE_HOST=db.example.com
DATABASE_PORT=5432
DATABASE_NAME=strapi_production
DATABASE_USERNAME=strapi_admin
DATABASE_PASSWORD=s3cr3t-p@ssword
DATABASE_SSL=true
APP_KEYS=key1,key2,key3,key4
API_TOKEN_SALT=random-salt-value
ADMIN_JWT_SECRET=admin-jwt-secret
JWT_SECRET=api-jwt-secret
TRANSFER_TOKEN_SALT=transfer-token-salt

Required Environment Variables

Strapi requires several environment variables for security. Each is generated during installation but should be overridden in production:

# APP_KEYS — Used for session cookies and encryption
# Must be 4 comma-separated values, each 32+ characters
APP_KEYS=skN2k3j4h5g6f7d8s9a0q1w2e3r4t5y6,u7i8o9p0a1s2d3f4g5h6j7k8l9z0x1c2,v3b4n5m6q7w8e9r0t1y2u3i4o5p6a7s8,d9f0g1h2j3k4l5z6x7c8v9b0n1m2q3w4

# API_TOKEN_SALT — Used to generate API tokens
API_TOKEN_SALT=5a8b3c2d1e9f4g7h6i0j2k3l4m5n6o7p

# ADMIN_JWT_SECRET — Used for admin panel JWT tokens
ADMIN_JWT_SECRET=8a9b0c1d2e3f4g5h6i7j8k9l0m1n2o3p

# JWT_SECRET — Used for user-facing API authentication
JWT_SECRET=1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p

# TRANSFER_TOKEN_SALT — Used for data transfer between instances
TRANSFER_TOKEN_SALT=q1w2e3r4t5y6u7i8o9p0a1s2d3f4g5h6

Generate secure random values for each environment:

# Generate 4 APP_KEYS
node -e "const crypto = require('crypto'); for(let i=0;i<4;i++) console.log(crypto.randomBytes(32).toString('hex'));"

# Generate a single secret
node -e "console.log(require('crypto').randomBytes(32).toString('hex'));"

Configuration Files That Use Environment Variables

Strapi's config files use the env() helper to read environment variables:

// config/database.js
module.exports = ({ env }) => ({
  connection: {
    client: env("DATABASE_CLIENT", "sqlite"),
    connection: {
      ...(env("DATABASE_CLIENT") === "sqlite"
        ? { filename: env("DATABASE_FILENAME", ".tmp/data.db") }
        : {
            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", "password"),
            ssl: env.bool("DATABASE_SSL", false),
          }),
    },
    pool: { min: 2, max: 10 },
  },
});
// config/server.js
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") },
    url: env("ADMIN_URL", "/admin"),
    serveAdminPanel: env.bool("SERVE_ADMIN", true),
  },
  url: env("PUBLIC_URL", "http://localhost:1337"),
  proxy: env.bool("IS_PROXY", false),
});
// config/plugins.js
module.exports = ({ env }) => ({
  upload: {
    config: {
      provider: "aws-s3",
      providerOptions: {
        accessKeyId: env("AWS_ACCESS_KEY_ID"),
        secretAccessKey: env("AWS_ACCESS_SECRET"),
        region: env("AWS_REGION", "us-east-1"),
        params: {
          Bucket: env("AWS_S3_BUCKET"),
        },
      },
      actionOptions: {
        upload: {},
        uploadStream: {},
        delete: {},
      },
    },
  },
  email: {
    config: {
      provider: "sendgrid",
      providerOptions: {
        apiKey: env("SENDGRID_API_KEY"),
      },
      settings: {
        defaultFrom: env("EMAIL_FROM", "noreply@example.com"),
        defaultReplyTo: env("EMAIL_REPLY_TO", "support@example.com"),
      },
    },
  },
});

The env() helper provides several methods:

env("VAR_NAME", "default_value")   // String (with default fallback)
env.int("VAR_NAME", 8080)           // Integer
env.bool("VAR_NAME", true)          // Boolean
env.array("VAR_NAME", ["default"])  // Comma-separated string → array
env.json("VAR_NAME", {})            // JSON string → object
env("VAR_NAME")                     // Required (throws if missing)

Multi-Environment Setup

Create separate environment configurations for each deployment stage:

# Project structure
.env                    # Shared defaults (committed)
.env.development        # Local development overrides
.env.staging            # Staging environment
.env.production         # Production secrets (never committed)
# .env.development — Developer-specific settings
HOST=localhost
PORT=1337
DATABASE_CLIENT=sqlite
DATABASE_FILENAME=.tmp/data.db
ADMIN_JWT_SECRET=dev-secret-key-not-for-production
JWT_SECRET=dev-jwt-secret
APP_KEYS=dev-key-one,dev-key-two,dev-key-three,dev-key-four
# .env.staging — Shared staging environment
NODE_ENV=production
DATABASE_CLIENT=postgres
DATABASE_HOST=staging-db.example.com
DATABASE_PORT=5432
DATABASE_NAME=strapi_staging
DATABASE_USERNAME=strapi_staging
DATABASE_PASSWORD=staging-password
DATABASE_SSL=true
PUBLIC_URL=https://staging-api.example.com

The .env.production file should never be committed to git. Add it to .gitignore:

# .gitignore
.env.production
.env.*.local

Managing Secrets in Production

For production, use a secrets manager instead of .env files:

# Option 1: Set environment variables in the hosting platform
# Railway, Heroku, DigitalOcean App Platform — set via dashboard or CLI

# Option 2: Use a secrets manager (Vault, AWS Secrets Manager)
# Fetch secrets at startup
// config/database.js — Using AWS Secrets Manager
const AWS = require("aws-sdk");

module.exports = async () => {
  if (process.env.NODE_ENV === "production") {
    const secrets = new AWS.SecretsManager({ region: "us-east-1" });
    const data = await secrets
      .getSecretValue({ SecretId: "strapi/production/db" })
      .promise();
    const dbConfig = JSON.parse(data.SecretString);

    return {
      connection: {
        client: "postgres",
        connection: {
          host: dbConfig.host,
          port: dbConfig.port,
          database: dbConfig.database,
          user: dbConfig.username,
          password: dbConfig.password,
          ssl: true,
        },
      },
    };
  }

  // Fall back to env vars for development
  return {
    connection: {
      client: "sqlite",
      connection: { filename: ".tmp/data.db" },
    },
  };
};

Validating Environment Variables

Set up validation to catch missing variables early:

// config/env-validation.js
const requiredVars = [
  "APP_KEYS",
  "API_TOKEN_SALT",
  "ADMIN_JWT_SECRET",
  "JWT_SECRET",
];

requiredVars.forEach((varName) => {
  if (!process.env[varName]) {
    throw new Error(
      `Missing required environment variable: ${varName}. ` +
        "Set it in your .env file or environment configuration."
    );
  }
});

// Validate APP_KEYS format
const keys = process.env.APP_KEYS.split(",");
if (keys.length !== 4) {
  throw new Error("APP_KEYS must contain exactly 4 comma-separated values.");
}
keys.forEach((key, i) => {
  if (key.length < 16) {
    throw new Error(
      `APP_KEYS[${i}] is too short. Each key must be at least 16 characters.`
    );
  }
});

Common Mistakes

  1. Committing .env.production to git. Production credentials in the Repository are a security breach. Anyone with repository access has your database password and API keys. Add .env.production to .gitignore immediately.

  2. Using default secret values in production. Strapi generates secrets during installation. Using the default values in production means anyone who knows the default can forge JWT tokens or decrypt sessions. Generate new secrets for production.

  3. Not setting PUBLIC_URL. Without PUBLIC_URL, Strapi generates incorrect URLs in emails, admin panel links, and API responses. Always set the full public URL: PUBLIC_URL=https://api.example.com.

  4. Forgetting the comma-separated format for APP_KEYS. APP_KEYS expects four comma-separated values, not a single key. Using one key causes session errors. Generate four separate keys.

  5. Hardcoding values in config files. Writing host: "localhost" directly in config/database.js means you cannot change the host without editing code. Always use env("DATABASE_HOST", "localhost") to keep configuration flexible.

Practice Questions

  1. What environment variables are required by Strapi and why? Answer: APP_KEYS (session encryption), API_TOKEN_SALT (API token generation), ADMIN_JWT_SECRET (admin panel auth), JWT_SECRET (user-facing auth), TRANSFER_TOKEN_SALT (data transfer). Each serves a specific security function.

  2. How does Strapi resolve the final value of an environment variable? Answer: Strapi loads .env (shared defaults), then .env.<NODE_ENV> (environment-specific overrides), then actual Process environment variables. The last value wins, so process.env takes highest priority.

  3. What happens if APP_KEYS is missing or has wrong format? Answer: Strapi throws an error during startup: session handling fails, admin panel login returns errors, and cookie encryption does not work. The app is unusable until APP_KEYS is properly configured.

  4. Challenge: Set up a complete multi-environment configuration system: (1) Create .env, .env.development, .env.staging, and .env.production files with appropriate values, (2) Configure config/database.js to switch between SQLite and PostgreSQL based on DATABASE_CLIENT, (3) Configure config/server.js to use environment variables for host, port, and public URL, (4) Configure config/plugins.js for S3 upload in production and local upload in development, (5) Add .env.production to .gitignore, (6) Write a validation script that checks all required variables are set at startup, (7) Test that the application works with each environment configuration.

FAQ

Where does Strapi look for .env files?

Strapi looks for .env files in the project root directory. It loads .env first, then .env.<NODE_ENV> if it exists. The files must be in the same directory as package.json.

Can I use YAML or JSON for Strapi configuration?

Strapi config files are JavaScript modules that export a function. You can use YAML or JSON by creating custom config loaders, but the standard approach is JavaScript with the env() helper.

How do I rotate secrets without downtime?

Generate new secrets, add them to the environment while keeping old ones valid, update APP_KEYS to include old and new keys, then remove old keys after sessions expire. For JWT secrets, tokens signed with the old secret become invalid immediately.

Should I use a .env file in Docker containers?

For Docker, pass environment variables through Docker Compose or the container runtime. Do not bake .env files into Docker images. Use environment: in docker-compose.yml or --env-file with docker run.

How do I debug environment variable issues?

Add temporary logging: console.log('DATABASE_HOST:', process.env.DATABASE_HOST); in config/database.js. Also check that the .env file is in the correct directory and that NODE_ENV is set correctly.

Mini Project

Your task: Build a complete multi-environment configuration system for Strapi.

  1. Create separate .env files for development, staging, and production.
  2. Configure config/database.js to handle all three database types (SQLite, PostgreSQL, MySQL) based on DATABASE_CLIENT.
  3. Generate and set all required security secrets (APP_KEYS, JWT_SECRET, etc.) for each environment.
  4. Set up the upload plugin to use local provider in development and S3 in production.
  5. Configure the email plugin with SendGrid in production, disabling it in development.
  6. Set up PUBLIC_URL correctly for each environment.
  7. Write a validation script that checks all required variables and reports missing ones with clear error messages.
  8. Deploy to staging and verify all configurations load correctly.

What's Next

Now that you understand environment variables, proceed to CI/CD to learn how to set up automated testing and deployment pipelines. After that, explore Performance to optimize your Strapi application.

Related lessons:

  • Node.js — Environment configuration best practices
  • Docker — Container environment variables
  • PostgreSQL — Database credentials management

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro