DeployWise
HomeGuidesEnv Variables in Production
Next.jsEnvironmentPM2Production

Next.js environment variables in production: the complete guide

Environment variables are one of the most common sources of production bugs in Next.js. This guide explains how server-side vs client-side variables work, how to set them on a VPS, and the mistakes that silently break production apps.

8 min read
Updated 2026

Server-side vs client-side variables

Next.js has two types of environment variables, and mixing them up is the #1 source of production env bugs:

Type
Prefix
Available in
Server-side
None (e.g. DATABASE_URL)
Server components, API routes, getServerSideProps only
Client-side
NEXT_PUBLIC_
Browser + server (bundled at build time)

Critical: NEXT_PUBLIC_ variables are embedded into the JavaScript bundle at build time. If they're wrong or missing when you run npm run build, they'll be wrong in production — even if you fix the .env file later without rebuilding.

The .env file hierarchy

Next.js loads env files in this priority order (higher overrides lower):

.env files (highest priority first)
.env.local              # Local overrides — never commit this
.env.production.local   # Production + local overrides
.env.development.local  # Development + local overrides
.env.production         # Production defaults — can commit
.env.development        # Development defaults — can commit
.env                    # All environments — can commit

For VPS production deployments, the recommended approach is .env.local — it's never committed to git, and it's loaded automatically in all environments.

Setting env variables on a VPS

Option 1: .env.local file (recommended)

Create the file directly on your server, outside of git:

bash
# SSH into your server, go to your app directory
cd /var/www/my-app

# Create the file
nano .env.local
.env.local
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
NEXTAUTH_SECRET=your-super-secret-key-here
NEXTAUTH_URL=https://yourdomain.com
NEXT_PUBLIC_APP_URL=https://yourdomain.com
STRIPE_SECRET_KEY=sk_live_...

Option 2: PM2 ecosystem config

For apps managed by PM2, you can define env vars directly in the ecosystem config:

ecosystem.config.js
module.exports = {
  apps: [{
    name: "my-app",
    script: "node_modules/.bin/next",
    args: "start",
    env: {
      NODE_ENV: "production",
      PORT: 3000,
    },
    env_production: {
      DATABASE_URL: "postgresql://user:pass@localhost:5432/mydb",
      NEXTAUTH_SECRET: "your-secret",
      NEXTAUTH_URL: "https://yourdomain.com",
    }
  }]
}
bash
# Start with production env
pm2 start ecosystem.config.js --env production
pm2 save

Common mistakes that break production

NEXT_PUBLIC_ variable is undefined in the browser

You set the variable after the build. NEXT_PUBLIC_ vars are embedded at build time — set them before running npm run build, then rebuild.

Environment variable works locally but is undefined on VPS

Your .env.local file doesn't exist on the server (it's gitignored). Create it manually on the VPS: nano /var/www/my-app/.env.local

PM2 app doesn't see new env variables after update

PM2 caches environment at startup. After changing .env.local, run: pm2 restart my-app (not reload — restart reloads the env).

process.env.MY_VAR works in API route but not in client component

Only NEXT_PUBLIC_ variables are exposed to the client. Rename to NEXT_PUBLIC_MY_VAR if it needs to be accessible in browser-side code.

Verifying env variables at runtime

bash
# Check if PM2 app sees the variables
pm2 env my-app

# Or SSH in and check manually
cd /var/www/my-app
node -e "require('dotenv').config({path: '.env.local'}); console.log(process.env.DATABASE_URL)"
Never log full env variable values in production — mask secrets in logs
Use a startup check in your app to validate required env vars on boot
Rotate secrets by updating .env.local + running pm2 restart (not reload)
Never commit .env.local or .env.production.local to git

DeployWise handles env variables for you

Set environment variables from the dashboard — DeployWise writes them to .env.local on your server and restarts PM2 automatically.

Try DeployWise Free