
How to Set Environment Variables in Linux (export, .bashrc, systemd)
Step-by-step guide to setting environment variables in Linux. Covers export for session variables, .bashrc and .zshrc for persistence, /etc/environment for system-wide config, and systemd EnvironmentFile for services.
How to Set Environment Variables in Linux
You SSH into a production server at 2 AM because the deployment failed. The application won't start. After digging through logs, you realize the problem: someone forgot to set DATABASE_URL on this instance. You need to set an environment variable in Linux, and you need it working now—not just for this session, but permanently.
Environment variables are fundamental to how Linux systems work. They control everything from where your shell looks for commands (PATH) to how applications connect to databases. Understanding how to set up environment variables in Linux properly means understanding the difference between temporary session variables, user-specific persistent variables, and system-wide configuration.
This guide covers the practical mechanics of setting environment variables across different scopes, shell configurations, and systemd services. You'll learn which approach to use depending on whether you're configuring your personal development environment or deploying applications to production servers.
Setting Variables with export
The export command sets environment variables in your current shell session. It's the most immediate way to define a variable, but it only lasts until you close your terminal or log out.
Basic syntax:
export VARIABLE_NAME="value"
Real example:
export DATABASE_URL="postgresql://user:pass@localhost:5432/mydb"
export API_KEY="sk_live_abc123xyz"
export NODE_ENV="production"
You can set multiple variables in sequence or chain them on one line:
export PORT=3000 HOSTNAME=api.example.com LOG_LEVEL=debug
To verify a variable is set, use echo:
echo $DATABASE_URL
Variables set with export are available to the current shell and any child processes it spawns. If you run a script or start an application from that terminal, it inherits those variables. Open a new terminal tab, though, and they're gone.
This approach is perfect for:
- Quick testing and debugging
- One-off commands that need specific configuration
- Temporary overrides of existing variables
But if you need the variable to persist across sessions, you need to add it to a shell configuration file.
Shell Configuration Files: Where to Put Persistent Variables
Linux shells read configuration files when they start. The file they read depends on whether the shell is a login shell, an interactive shell, or a non-interactive shell. This is where things get confusing.
Login vs Interactive vs Non-Interactive Shells
A login shell runs when you log in via SSH or a TTY console. It reads /etc/profile, then ~/.bash_profile, ~/.bash_login, or ~/.profile (in that order, stopping at the first one it finds).
An interactive non-login shell runs when you open a new terminal window in your desktop environment. It reads ~/.bashrc.
A non-interactive shell runs when you execute a script. It doesn't read the standard config files unless the script explicitly sources them.
The Files Explained
~/.bashrc: Sourced by interactive non-login shells. This is where most people put their aliases, functions, and environment variables for daily use. If you're using a graphical terminal emulator, this is what gets loaded every time you open a new terminal window.
~/.bash_profile: Sourced by login shells. It typically sources ~/.bashrc to ensure consistency between login and non-login shells. Many people put export PATH modifications here, but honestly, just putting them in ~/.bashrc and sourcing that from ~/.bash_profile is cleaner.
~/.profile: A POSIX-compliant fallback used by shells other than Bash (like sh or dash). If you're writing shell-agnostic configuration, use this. But if you're running Bash, ~/.bashrc and ~/.bash_profile are more specific.
~/.zshrc: If you're using Zsh (the default on macOS since Catalina, and increasingly popular on Linux), this is your main configuration file. Zsh doesn't distinguish between login and non-login shells the same way Bash does—it just uses ~/.zshrc for interactive shells.
Opinionated Take: Just Use .bashrc
If you're unsure where to put variables, put them in ~/.bashrc and add this to the top of your ~/.bash_profile:
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
This ensures variables are available in both login and interactive shells. You maintain one file, and everything works.
Setting Variables in Shell Config Files
Open ~/.bashrc (or ~/.zshrc if you use Zsh):
nano ~/.bashrc
Add your variables at the bottom:
export EDITOR=vim
export VISUAL=vim
export BROWSER=firefox
export GOPATH="$HOME/go"
export PATH="$GOPATH/bin:$PATH"
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
Save the file and reload it:
source ~/.bashrc
Now those variables persist across all future terminal sessions.
System-Wide Environment Variables
User-specific config files like ~/.bashrc only affect your user account. If you're setting variables that should apply to all users on the system (like JAVA_HOME or custom PATH additions for system-wide tools), you need system-wide configuration.
/etc/environment
This is the simplest system-wide configuration file. It's read by PAM (Pluggable Authentication Modules) during login and applies to all users regardless of their shell.
Syntax is straightforward—no export needed:
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
LANG="en_US.UTF-8"
Variables here are static. You can't use shell expansions like $HOME or $PATH because /etc/environment isn't processed by a shell.
/etc/profile
This file is sourced by login shells for all users. It's a shell script, so you can use export, variable expansions, and conditionals.
export EDITOR=nano
export JAVA_HOME=/usr/lib/jvm/default-java
# Add custom binary directory to PATH
if [ -d /opt/custom/bin ]; then
export PATH="/opt/custom/bin:$PATH"
fi
Changes here require users to log out and back in (or source the file manually).
/etc/profile.d/
Instead of editing /etc/profile directly, it's cleaner to drop custom scripts into /etc/profile.d/. The system automatically sources all .sh files in this directory during login.
Create a custom config file:
sudo nano /etc/profile.d/custom-env.sh
Add your variables:
export APP_ENV=production
export LOG_LEVEL=info
export DATA_DIR=/srv/data
Make it executable:
sudo chmod +x /etc/profile.d/custom-env.sh
This approach keeps customizations modular. Package managers can drop their own environment scripts here without conflicting with system defaults.
Which System-Wide File to Use?
/etc/environment: For simple, static variables that don't need shell logic. Applies to all login mechanisms (SSH, GUI, TTY)./etc/profile: For variables that need shell expansion or conditionals. Only applies to login shells./etc/profile.d/: Best practice for custom system-wide variables. Keeps configuration modular and maintainable.
Environment Variables in Systemd Services
When you run applications as systemd services, they don't inherit your shell's environment. You need to define variables directly in the service unit file.
Inline Environment Variables
Open or create a service file:
sudo nano /etc/systemd/system/myapp.service
Use the Environment= directive:
[Unit]
Description=My Application
After=network.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/start
Environment="NODE_ENV=production"
Environment="PORT=3000"
Environment="DATABASE_URL=postgresql://localhost/mydb"
[Install]
WantedBy=multi-user.target
Each Environment= line sets one variable. Quotes are optional but recommended for values with special characters.
Environment Files
For services with many variables or sensitive configuration, use EnvironmentFile= to load variables from a file:
[Service]
EnvironmentFile=/etc/myapp/env
ExecStart=/opt/myapp/bin/start
Create the environment file:
sudo nano /etc/myapp/env
Use simple KEY=VALUE pairs (no export):
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:pass@localhost/mydb
API_KEY=sk_live_abc123xyz
LOG_LEVEL=info
Protect sensitive files with proper permissions:
sudo chown root:root /etc/myapp/env
sudo chmod 600 /etc/myapp/env
After editing service files, reload systemd and restart the service:
sudo systemctl daemon-reload
sudo systemctl restart myapp
Using EnvironmentFile= keeps secrets out of service definitions and makes it easier to manage configuration across environments (dev, staging, production).
Viewing Environment Variables
Different commands show different scopes of variables. Knowing which to use matters when debugging.
printenv
Displays environment variables available to the current process:
printenv
Show a specific variable:
printenv PATH
printenv HOME
This is the most common command for checking environment variables. It only shows exported variables (not shell-local variables).
env
Similar to printenv, but can also set variables for a single command:
env NODE_ENV=development npm start
This runs npm start with NODE_ENV set to development for that command only—it doesn't persist.
Run env without arguments to list all environment variables:
env
set
Shows all variables—both environment variables and shell-local variables. The output is verbose because it includes shell functions and local variables.
set | grep DATABASE_URL
Use set when you're debugging and need to see everything. For most purposes, printenv is cleaner.
Persistent vs Session-Scoped Variables
Understanding scope is critical:
| Scope | Method | Duration | Use Case |
|---|---|---|---|
| Current session only | export VAR=value | Until terminal closes | Testing, debugging, one-off commands |
| User-specific, persistent | Add to ~/.bashrc or ~/.zshrc | Every new shell session | Personal development environment |
| User-specific, login only | Add to ~/.bash_profile | Login shells only | Variables that only need to be set once per login |
| System-wide, all users | /etc/environment or /etc/profile.d/ | All user sessions | System tools, language runtimes (JAVA_HOME, etc.) |
| Systemd service | Environment= or EnvironmentFile= | Service lifetime | Application configuration, database URLs |
For quick experimentation, use export. For personal development, use ~/.bashrc. For system-wide configuration, use /etc/profile.d/. For services, use systemd environment files.
Practical Examples
Modifying PATH
The PATH variable tells the shell where to look for executable files. Adding a custom directory:
export PATH="$HOME/bin:$PATH"
This prepends ~/bin to the existing PATH. Order matters—directories at the front are searched first.
To make it persistent, add it to ~/.bashrc:
# Custom scripts directory
export PATH="$HOME/bin:$PATH"
For system-wide changes (like adding /opt/custom/bin for all users):
sudo nano /etc/profile.d/custom-path.sh
export PATH="/opt/custom/bin:$PATH"
Application Configuration
Set database connection strings, API keys, and application modes:
export DATABASE_URL="postgresql://user:pass@localhost:5432/mydb"
export REDIS_URL="redis://localhost:6379/0"
export API_KEY="sk_live_abc123xyz"
export APP_ENV="production"
For a Node.js app running as a systemd service:
sudo nano /etc/systemd/system/nodeapp.service
[Service]
EnvironmentFile=/etc/nodeapp/env
ExecStart=/usr/bin/node /opt/nodeapp/server.js
sudo nano /etc/nodeapp/env
NODE_ENV=production
DATABASE_URL=postgresql://user:pass@localhost:5432/nodedb
PORT=3000
Language Runtime Configuration
Java applications need JAVA_HOME:
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export PATH="$JAVA_HOME/bin:$PATH"
Python virtual environments activate by modifying PATH:
source ~/projects/myapp/venv/bin/activate
This prepends the virtual environment's bin directory to PATH, so python and pip resolve to the virtual environment's copies.
Go projects use GOPATH:
export GOPATH="$HOME/go"
export PATH="$GOPATH/bin:$PATH"
Add these to ~/.bashrc if you work with these languages regularly.
Conditional Environment Variables
Sometimes you want variables set only under specific conditions. In ~/.bashrc:
# Use system-specific config
if [ -f ~/.env.local ]; then
source ~/.env.local
fi
# Development shortcuts
if [ "$USER" = "dev" ]; then
export DEBUG=true
export LOG_LEVEL=debug
fi
# Production safety
if [ "$HOSTNAME" = "prod-server" ]; then
export NODE_ENV=production
export DEBUG=false
fi
This pattern keeps environment-specific configuration clean and maintainable.
Managing Variables Across Multiple Environments
Manually editing config files on every server gets tedious fast. Production, staging, and development environments each need different values for DATABASE_URL, API_KEY, and dozens of other variables. You end up SSHing into servers, editing /etc/myapp/env, restarting services, and hoping you didn't typo a connection string.
For teams managing environment variables across multiple projects and environments, EnvManager (free tier available) centralizes this workflow so you're not manually editing config files on every server. You define variables once, organize them by environment, and pull them into your deployment pipeline. It's the difference between maintaining scattered .env files across 20 servers and having a single source of truth.
But whether you use a dedicated tool or stick with config files, the underlying Linux mechanisms are the same. Understanding export, shell config files, system-wide settings, and systemd environment files gives you the foundation to troubleshoot when things break at 2 AM.
Related Reading
- How to Set Environment Variables — General guide covering all operating systems
- How to Set Environment Variables in Windows — Windows-specific configuration methods
- Environment Variables in PowerShell — PowerShell-specific commands, scopes, and profiles
- Environment Variables in Python — Working with environment variables in Python applications
- Environment Variables in Java — System.getenv(), Spring Boot, and .env loading
- Git Environment Variables — Identity, SSH keys, debugging, and CI/CD usage