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.
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:
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.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:
# SSH into your server, go to your app directory cd /var/www/my-app # Create the file nano .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:
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",
}
}]
}# Start with production env pm2 start ecosystem.config.js --env production pm2 save
Common mistakes that break production
✅ You set the variable after the build. NEXT_PUBLIC_ vars are embedded at build time — set them before running npm run build, then rebuild.
✅ 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 caches environment at startup. After changing .env.local, run: pm2 restart my-app (not reload — restart reloads the env).
✅ 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
# 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)"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