Skip to main content

Introduction

Proper environment configuration is critical for security and functionality in production. This guide covers environment variables for both backend and frontend, security considerations, and common configuration patterns.

Backend Environment Variables

Core Configuration

Create a production .env file in your Laravel backend:
.env
# Application
APP_NAME="Your Application Name"
APP_ENV=production
APP_KEY=base64:your-generated-key-here
APP_DEBUG=false
APP_URL=https://api.yourdomain.com

# Frontend Configuration
FRONTEND_URL=https://app.yourdomain.com

# Timezone and Locale
APP_TIMEZONE=UTC
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
Never set APP_DEBUG=true in production. This exposes sensitive information about your application.

Database Configuration

.env
# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_production_database
DB_USERNAME=your_production_user
DB_PASSWORD=your_secure_password
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=production_db
DB_USERNAME=db_user
DB_PASSWORD=strong_password_here

Session Configuration

.env
# Session
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=.yourdomain.com
Set SESSION_DOMAIN to your root domain with a leading dot (.yourdomain.com) to share sessions across subdomains.

Cache Configuration

.env
# Cache
CACHE_STORE=redis
CACHE_PREFIX=myapp

# Redis
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_DB=0

Queue Configuration

.env
# Queue
QUEUE_CONNECTION=redis

# If using database queue
# QUEUE_CONNECTION=database

Mail Configuration

.env
# Mail
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=your_username
MAIL_PASSWORD=your_password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="${APP_NAME}"
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=[email protected]
MAIL_PASSWORD=your_app_password
MAIL_ENCRYPTION=tls

Logging Configuration

.env
# Logging
LOG_CHANNEL=stack
LOG_STACK=daily
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=error
Set LOG_LEVEL=error in production to reduce log noise. Use debug only for troubleshooting.

Frontend Environment Variables

Next.js Configuration

Create .env.production in your Next.js frontend:
.env.production
# Backend API URL (Public - embedded in client bundle)
NEXT_PUBLIC_BACKEND_URL=https://api.yourdomain.com

# Application Name (Public)
NEXT_PUBLIC_APP_NAME="Your App Name"

# Server-only variables (NOT exposed to browser)
API_SECRET_KEY=your_server_secret
Only variables prefixed with NEXT_PUBLIC_ are accessible in the browser. Never prefix secrets or API keys with NEXT_PUBLIC_.

Environment Variable Validation

Create a validation file to ensure required variables are set:
lib/env.ts
function getEnvVar(key: string, defaultValue?: string): string {
  const value = process.env[key] ?? defaultValue
  
  if (!value) {
    throw new Error(`Missing required environment variable: ${key}`)
  }
  
  return value
}

function getPublicEnvVar(key: string, defaultValue?: string): string {
  if (!key.startsWith('NEXT_PUBLIC_')) {
    throw new Error(`Public environment variable must start with NEXT_PUBLIC_: ${key}`)
  }
  return getEnvVar(key, defaultValue)
}

export const env = {
  // Public variables
  backendUrl: getPublicEnvVar('NEXT_PUBLIC_BACKEND_URL'),
  appName: getPublicEnvVar('NEXT_PUBLIC_APP_NAME', 'My App'),
  
  // Server-only variables
  apiSecret: getEnvVar('API_SECRET_KEY'),
} as const

// Validate at build time
if (typeof window === 'undefined') {
  console.log('✓ Environment variables validated')
}
Usage:
import { env } from '@/lib/env'

const response = await fetch(`${env.backendUrl}/api/user`)

CORS Configuration

Backend CORS Setup

The CORS configuration is in config/cors.php:
config/cors.php
return [
    'paths' => ['api/*', 'sanctum/csrf-cookie'],

    'allowed_methods' => ['*'],

    'allowed_origins' => [
        env('FRONTEND_URL', 'http://localhost:3000'),
    ],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    'supports_credentials' => true,
];
Set supports_credentials to true for Sanctum authentication. Ensure allowed_origins matches your frontend URL exactly.

Multiple Frontend URLs

If you have multiple frontend domains (staging, production):
config/cors.php
'allowed_origins' => [
    env('FRONTEND_URL'),
    env('FRONTEND_URL_STAGING'),
    'https://app.yourdomain.com',
    'https://staging.yourdomain.com',
],
.env
FRONTEND_URL=https://app.yourdomain.com
FRONTEND_URL_STAGING=https://staging.yourdomain.com

Sanctum Configuration

Backend Sanctum Setup

.env
# Sanctum
SANCTUM_STATEFUL_DOMAINS=app.yourdomain.com,localhost:3000
SESSION_DOMAIN=.yourdomain.com
In config/sanctum.php:
config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,localhost:3000')),

'guard' => ['web'],

'expiration' => null,

'middleware' => [
    'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
    'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
],

Frontend Axios Configuration

Configure Axios to send cookies:
lib/axios.ts
import Axios from 'axios'

const axios = Axios.create({
  baseURL: process.env.NEXT_PUBLIC_BACKEND_URL,
  headers: {
    'X-Requested-With': 'XMLHttpRequest',
  },
  withCredentials: true, // Required for Sanctum
})

export default axios

API URL Configuration

Development vs Production

Backend (.env)
APP_URL=http://localhost:8000
FRONTEND_URL=http://localhost:3000
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost:3000
Frontend (.env.local)
NEXT_PUBLIC_BACKEND_URL=http://localhost:8000

Security Best Practices

# Generate new application key
php artisan key:generate

# Or manually
php -r "echo 'base64:'.base64_encode(random_bytes(32)).PHP_EOL;"
Never share or commit your APP_KEY. If exposed, regenerate immediately.
  • Use strong passwords (16+ characters, mixed case, numbers, symbols)
  • Create database users with minimal privileges
  • Use different credentials for each environment
  • Store credentials in secure vault (e.g., AWS Secrets Manager)
# Good password example
DB_PASSWORD=Xy9$mK2#nP5@qR8&vT4

# Bad password examples
DB_PASSWORD=password123
DB_PASSWORD=admin
config/cors.php
// ❌ DON'T: Allow all origins
'allowed_origins' => ['*'],

// ✅ DO: Specify exact origins
'allowed_origins' => [
    'https://app.yourdomain.com',
    'https://staging.yourdomain.com',
],
# Secure .env file
chmod 600 .env
chown www-data:www-data .env

# Verify permissions
ls -la .env
# Output: -rw------- 1 www-data www-data
Don’t use the same .env file across environments:
.env.development
.env.staging
.env.production
Add all to .gitignore:
.env
.env.*
!.env.example

Common Configuration Patterns

Full Production Backend .env

.env
# Application
APP_NAME="Your App Name"
APP_ENV=production
APP_KEY=base64:your-generated-key-here
APP_DEBUG=false
APP_URL=https://api.yourdomain.com
FRONTEND_URL=https://app.yourdomain.com

APP_TIMEZONE=UTC
APP_LOCALE=en

# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=production_db
DB_USERNAME=db_user
DB_PASSWORD=secure_password_here

# Session & Cookies
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_DOMAIN=.yourdomain.com

# Sanctum
SANCTUM_STATEFUL_DOMAINS=app.yourdomain.com

# Cache & Queue
CACHE_STORE=redis
QUEUE_CONNECTION=redis

# Redis
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=redis_password_here
REDIS_PORT=6379

# Mail
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=your_username
MAIL_PASSWORD=your_password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="${APP_NAME}"

# Logging
LOG_CHANNEL=stack
LOG_STACK=daily
LOG_LEVEL=error

# Broadcasting
BROADCAST_CONNECTION=log

# Filesystem
FILESYSTEM_DISK=local

Full Production Frontend .env.production

.env.production
# Backend API
NEXT_PUBLIC_BACKEND_URL=https://api.yourdomain.com

# Application
NEXT_PUBLIC_APP_NAME="Your App Name"
NEXT_PUBLIC_APP_VERSION="1.0.0"

# Analytics (optional)
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX

# Feature Flags (optional)
NEXT_PUBLIC_ENABLE_ANALYTICS=true

Environment Variable Management Tools

Doppler

Centralized environment variable management
  • Sync across environments
  • Team collaboration
  • Audit logs
  • Secret rotation

AWS Secrets Manager

AWS-native secret management
  • Automatic rotation
  • Fine-grained access control
  • Encryption at rest
  • Integration with AWS services

HashiCorp Vault

Enterprise secret management
  • Dynamic secrets
  • Encryption as a service
  • Secret leasing
  • Audit logging

Dotenv Vault

Encrypted .env files in version control
  • Encrypted commits
  • Team synchronization
  • Multiple environments
  • Simple integration

Troubleshooting

Symptoms:
  • “No ‘Access-Control-Allow-Origin’ header”
  • Authentication fails
  • Cookies not set
Solutions:
  1. Verify FRONTEND_URL in backend .env
  2. Check allowed_origins in config/cors.php
  3. Ensure supports_credentials is true
  4. Clear Laravel config cache: php artisan config:clear
  5. Verify both apps use HTTPS in production
Symptoms:
  • Login succeeds but user not authenticated
  • Cookies not being set
  • CSRF token mismatch
Solutions:
  1. Check SESSION_DOMAIN is set correctly (.yourdomain.com)
  2. Verify SANCTUM_STATEFUL_DOMAINS includes frontend domain
  3. Ensure axios has withCredentials: true
  4. Verify HTTPS is enabled
  5. Check browser DevTools → Application → Cookies
Backend (Laravel):
# Clear all caches
php artisan config:clear
php artisan cache:clear
php artisan route:clear
php artisan view:clear

# Verify .env is readable
ls -la .env

# Test variable
php artisan tinker
>>> env('APP_URL')
Frontend (Next.js):
# Rebuild application
rm -rf .next
npm run build

# Verify variables
echo $NEXT_PUBLIC_BACKEND_URL
Check Connection:
# Test database connection
php artisan tinker
>>> DB::connection()->getPdo();

# Check credentials
mysql -h 127.0.0.1 -u db_user -p
Common Issues:
  • Wrong database host or port
  • Incorrect credentials
  • Database server not running
  • Firewall blocking connection
  • Missing database

Checklist

Before deploying to production:
1

Backend Environment

  • Set APP_ENV=production
  • Set APP_DEBUG=false
  • Generate unique APP_KEY
  • Configure production database
  • Set correct APP_URL and FRONTEND_URL
  • Configure SESSION_DOMAIN and SANCTUM_STATEFUL_DOMAINS
  • Set up mail configuration
  • Configure cache and queue drivers
  • Set LOG_LEVEL=error
2

Frontend Environment

  • Set NEXT_PUBLIC_BACKEND_URL to production API
  • Remove development-only variables
  • Validate all required variables are set
  • Test build locally: npm run build && npm start
3

Security

  • Verify .env is not in version control
  • Set proper file permissions (600 for .env)
  • Use strong passwords for all services
  • Restrict CORS to specific origins
  • Enable HTTPS on both apps
4

Testing

  • Test authentication flow
  • Verify API connectivity
  • Check CORS headers
  • Test email sending
  • Verify queue processing

Next Steps

Backend Deployment

Deploy your Laravel backend with these configurations

Frontend Deployment

Deploy your Next.js frontend

Authentication

Learn about Laravel Sanctum authentication

Architecture

Understand the full-stack architecture

Build docs developers (and LLMs) love