Environment Variables: Secure Configuration Management
Use environment variables to manage secrets, API keys, database credentials. Keep sensitive data out of code. Setup .env files, systemd variables.
On this page
Environment variables are dynamic values that configure how applications run. They store sensitive information (API keys, database passwords, secrets) outside your code. This separates configuration from code, making apps more secure, portable, and flexible across development/staging/production environments.
What Are Environment Variables
Simple concept:
- Key-value pairs (name=value)
- Accessible by running processes
- Persist across process restarts
- Can be system-wide or process-specific
Examples:**
DATABASE_URL=mysql://user:pass@localhost/mydb
API_KEY=sk-1234567890abcdef
NODE_ENV=production
DEBUG=false
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
Why Use Environment Variables
The problem: secrets in code**
# ❌ BAD: Password in code (never do this!)
$db_password = "SuperSecret123!";
$api_key = "sk-1234567890abcdef";
The solution: environment variables**
# ✅ GOOD: Read from environment
$db_password = $_ENV['DB_PASSWORD'];
$api_key = $_ENV['API_KEY'];
Benefits:**
- Security: Secrets never in code, not in Git
- Flexibility: Different values for dev/staging/production
- Portability: Same code runs anywhere with different configs
- Simplicity: Change config without redeploying code
Read Environment Variables
In PHP:**
// Read from $_ENV superglobal
$database_url = $_ENV['DATABASE_URL'];
$api_key = $_ENV['API_KEY'];
// Or use getenv()
$database_url = getenv('DATABASE_URL');
// With default fallback
$debug = getenv('DEBUG') ?: 'false';
In Node.js:**
// Read from process.env
const dbUrl = process.env.DATABASE_URL;
const apiKey = process.env.API_KEY;
// With default
const debug = process.env.DEBUG || 'false';
In Python:**
import os
# Read from environment
db_url = os.environ['DATABASE_URL']
api_key = os.environ.get('API_KEY', 'default-value')
In Bash:**
#!/bin/bash
# Read from environment
echo $DATABASE_URL
echo $API_KEY
Set Environment Variables
Temporarily in current shell session:**
export DATABASE_URL="mysql://user:pass@localhost/db"
export API_KEY="sk-1234567890"
# Verify
echo $DATABASE_URL
# Variables disappear when shell closes
Permanently in user profile (~/.bashrc or ~/.bash_profile):**
echo 'export DATABASE_URL="mysql://user:pass@localhost/db"' >> ~/.bashrc
source ~/.bashrc
System-wide in /etc/environment:**
sudo nano /etc/environment
# Add: DATABASE_URL="mysql://user:pass@localhost/db"
# Then logout and login
.env Files
Best practice: use .env file for application configuration**
Create .env in project root:**
# .env file (DO NOT COMMIT TO GIT)
NODE_ENV=production
DATABASE_URL=mysql://user:pass@localhost/mydb
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
API_KEY=sk-1234567890abcdef
DEBUG=false
Load in PHP using dotenv package:**
# Install composer package
composer require vlucas/phpdotenv
# In app.php
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
// Now access variables
$db_url = $_ENV['DATABASE_URL'];
Load in Node.js using dotenv package:**
# Install npm package
npm install dotenv
# In app.js
require('dotenv').config();
// Now access variables
const dbUrl = process.env.DATABASE_URL;
Load in Python using python-dotenv:**
# Install pip package
pip install python-dotenv
# In app.py
from dotenv import load_dotenv
load_dotenv()
# Now access variables
db_url = os.environ['DATABASE_URL']
CRITICAL: Add .env to .gitignore**
# .gitignore (prevent accidental commit)
.env
.env.local
.env.*.local
Systemd Variables
For systemd services (PM2, Node.js, Python apps):**
Create systemd service file:**
# /etc/systemd/system/myapp.service
[Unit]
Description=My Node.js Application
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node app.js
# Set environment variables for this service
Environment="NODE_ENV=production"
Environment="DATABASE_URL=mysql://user:pass@localhost/db"
Environment="API_KEY=sk-1234567890"
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Start service:**
sudo systemctl daemon-reload
sudo systemctl start myapp
sudo systemctl enable myapp
Best Practices
- Never commit secrets: .env files go in .gitignore
- Use strong secrets: Long, random API keys and passwords
- Rotate secrets regularly: Change API keys every 90 days
- Document required variables: Create .env.example with placeholder values
- Use different values per environment: Dev, staging, production have separate secrets
- Don't log secrets: Never print API keys or passwords in logs
- Use vaults for production: AWS Secrets Manager, HashiCorp Vault for large deployments
Example .env.example (commit this, not .env):**
# Copy to .env and fill in real values
NODE_ENV=development
DATABASE_URL=mysql://user:password@localhost/database
API_KEY=your-api-key-here
REDIS_HOST=127.0.0.1
DEBUG=true
Troubleshooting
Environment variable not found / undefined:**
- Check variable is set:
echo $VAR_NAME - Verify .env file exists in correct directory
- Confirm dotenv is loaded before accessing variable
- Check for typos in variable name
Changes not taking effect:**
- Reload shell:
source ~/.bashrc - Restart application after changing .env
- Reload systemd service:
sudo systemctl restart myapp
Secrets exposed in logs or errors:**
- Never print env vars in debug output
- Use *** masking in error messages
- Sanitize logs before sharing
Once a secret is committed, it's in Git history forever. Use .env files outside of Git. Even if you delete the file later, it's still in commit history for anyone with repo access.
Related: Secrets management | Security hardening | Configuration management | Docker environment
Need a developer-friendly server?
Use an UnderHost Cloud VPS for SSH, Git, Node.js, Python, Laravel, Docker, cron, and custom development workflows.





















