Tutorial

Environment Variables on Linux: Set, Export, Persist, and Secure

April 25, 2026

Back to Blog

Environment variables are the invisible backbone of every Linux system. They configure how programs find libraries, where commands live, what language to use, and how applications connect to databases. Yet many administrators treat them as black magic — copying export commands from Stack Overflow without understanding why they work or where they should go. In this guide, we will demystify environment variables completely: how to set them, where to put them, how to make them persist across sessions and reboots, and critically, how to handle sensitive values like API keys and database passwords securely.

What Are Environment Variables?

An environment variable is a named value that lives in the shell's memory and is inherited by child processes. When you launch a program from the terminal, it receives a copy of all currently defined environment variables. This is how programs know where to find configuration without hardcoding paths or values.

$ echo $HOME /root $ echo $USER root $ echo $SHELL /bin/bash $ echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

These variables were set when you logged in, and every command you run inherits them. The $PATH variable is perhaps the most important — it tells the shell where to look for executable programs.

Viewing Environment Variables

There are several commands to inspect the current environment.

$ env # List all exported environment variables $ printenv # Same as env (mostly) $ printenv HOME # Print a specific variable /root $ set # List ALL variables (env + shell-local + functions) $ env | wc -l # Count how many env vars exist 24
env vs. set: The env command shows only exported environment variables (passed to child processes). The set command shows everything: exported variables, local shell variables, and shell functions. For debugging, env is usually what you want.

Setting Variables: export vs. Plain Assignment

This is the most commonly confused concept. There are two ways to assign a variable, and they behave very differently.

Plain Assignment (Shell-Only)

$ MY_VAR="hello" $ echo $MY_VAR hello $ bash -c 'echo $MY_VAR' # Child process cannot see it! (empty)

A plain assignment creates a shell variable. It exists only in the current shell session and is NOT inherited by child processes (programs, scripts, subshells).

export (Environment Variable)

$ export MY_VAR="hello" $ bash -c 'echo $MY_VAR' # Child process CAN see it! hello $ python3 -c 'import os; print(os.environ["MY_VAR"])' hello

The export command marks the variable for inheritance. Now every program launched from this shell receives it. This is essential when configuring applications that read their settings from the environment.

MethodVisible in Current ShellVisible in Child ProcessesSurvives Logout
MY_VAR="value"YesNoNo
export MY_VAR="value"YesYesNo
In ~/.bashrcYesYesYes (per user)
In /etc/environmentYesYesYes (system-wide)

Where to Put Environment Variables

This is where most confusion lives. Linux has multiple configuration files for environment variables, each loaded at different times and for different purposes.

Per-User Configuration Files

~/.bashrc Most Common

Executed for every new interactive, non-login shell. This includes every new terminal tab, every bash subshell, and most SSH sessions (which source it indirectly).

Best for: Aliases, functions, prompt customization, PATH additions, exported variables for interactive use.

~/.profile

Executed for login shells. This runs once when you first log in (SSH, console login). On Ubuntu, .profile sources .bashrc automatically.

Best for: Variables that should be set once at login. Ubuntu default .profile adds $HOME/bin and $HOME/.local/bin to PATH.

~/.bash_profile

If this file exists, bash reads it INSTEAD of ~/.profile for login shells. Most Ubuntu systems do not have this file by default.

Best for: Only if you need bash-specific login behavior that differs from .profile.

~/.bash_logout

Executed when a login shell exits. Rarely used for environment variables, but useful for cleanup tasks (clearing screen, logging).

Best for: Cleanup, not configuration.

The loading order matters: For a bash login shell: /etc/profile~/.bash_profile (or ~/.profile if no bash_profile) → ~/.bashrc (sourced by profile). For a non-login interactive shell: only ~/.bashrc. This is why putting everything in ~/.bashrc is usually the safest approach.

System-Wide Configuration

FileScopeFormatWhen Loaded
/etc/environmentAll users, all sessionsKEY=value (no export)PAM login
/etc/profileAll users, login shellsShell scriptLogin
/etc/profile.d/*.shAll users, login shellsShell scriptsSourced by /etc/profile
/etc/bash.bashrcAll users, interactive bashShell scriptEvery interactive bash

Practical Examples

# Per-user: Add to ~/.bashrc export EDITOR=vim export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin # System-wide: Add to /etc/environment JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 GO_ENV=production # System-wide script: Create /etc/profile.d/custom.sh export APP_HOME=/opt/myapp export PATH=$PATH:$APP_HOME/bin
Pro tip: For custom system-wide variables, create a file in /etc/profile.d/ rather than editing /etc/profile directly. Files in profile.d/ are sourced automatically and survive package updates that might overwrite /etc/profile.

The PATH Variable: Deep Dive

PATH is the most critical environment variable. It is a colon-separated list of directories where the shell looks for commands.

$ echo $PATH | tr ':' '\n' /usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin /snap/bin

When you type nginx, the shell searches these directories left to right until it finds a match. Order matters — earlier directories take priority.

Adding to PATH

# Append to PATH (new dir searched LAST) export PATH=$PATH:/opt/myapp/bin # Prepend to PATH (new dir searched FIRST - takes priority) export PATH=/opt/myapp/bin:$PATH # Verify a command is found $ which myapp /opt/myapp/bin/myapp $ type -a python3 # Show ALL locations python3 is /usr/bin/python3 python3 is /usr/local/bin/python3
Security warning: Never add the current directory (.) to your PATH, especially for root. An attacker could place a malicious script named ls or cat in a directory, and you would execute it unknowingly. Always use absolute paths or dedicated bin directories.

Environment Variables for Applications

The .env File Pattern

Modern web applications (Laravel, Django, Node.js, Rails) use .env files to store configuration. These are NOT automatically loaded by the shell — the application reads them using a library like dotenv.

$ cat /opt/myapp/.env APP_ENV=production APP_DEBUG=false DB_HOST=localhost DB_PORT=5432 DB_DATABASE=myapp DB_USERNAME=myapp_user DB_PASSWORD=s3cur3p4ssw0rd REDIS_HOST=127.0.0.1 MAIL_HOST=smtp.mailgun.org [email protected] MAIL_PASSWORD=key-abc123def456

Systemd EnvironmentFile

When running applications as systemd services, use EnvironmentFile to load variables from a file.

$ cat /etc/myapp/env DATABASE_URL=postgresql://user:pass@localhost:5432/myapp JWT_SECRET=your-secret-key-here LOG_LEVEL=info $ cat /etc/systemd/system/myapp.service [Service] EnvironmentFile=/etc/myapp/env ExecStart=/opt/myapp/bin/server

The EnvironmentFile directive loads the file and makes all variables available to the service process. The format is simple KEY=value pairs, one per line. The - prefix (e.g., EnvironmentFile=-/etc/myapp/env) means "do not fail if the file does not exist."

Inline Environment for a Single Command

# Set a variable for one command only $ DATABASE_URL="postgres://..." /opt/myapp/bin/migrate $ NODE_ENV=production npm start $ LANG=C sort largefile.txt # Faster sort without locale

Prefixing a command with KEY=value sets that variable only for the duration of that single command. The current shell is unaffected.

Common System Environment Variables

VariableDescriptionTypical Value
HOMECurrent user's home directory/root or /home/username
USERCurrent usernameroot
SHELLDefault shell/bin/bash
PATHExecutable search directories/usr/local/bin:/usr/bin:...
LANGSystem language/localeen_US.UTF-8
LC_ALLOverride all locale settingsC.UTF-8
TERMTerminal typexterm-256color
EDITORDefault text editorvim or nano
HISTSIZENumber of commands in history1000
TZTimezoneUTC or America/New_York
SSH_CONNECTIONSSH client IP and port192.168.1.50 54321 10.0.0.1 22
PWDPresent working directory/opt/myapp

Locale Variables Explained

Locale variables control language, formatting, and character encoding. Misconfigured locales cause broken characters in logs, incorrect date formats, and slow sorting operations.

$ locale LANG=en_US.UTF-8 LC_CTYPE="en_US.UTF-8" LC_NUMERIC="en_US.UTF-8" LC_TIME="en_US.UTF-8" LC_COLLATE="en_US.UTF-8" LC_MONETARY="en_US.UTF-8" LC_MESSAGES="en_US.UTF-8" LC_ALL=
Server tip: Set LC_ALL=C.UTF-8 for servers that do not need locale-aware formatting. The C locale is faster for operations like sort, grep, and string comparison because it skips complex Unicode collation rules. Use LANG=en_US.UTF-8 if you need proper language-aware behavior.

Securing Sensitive Environment Variables

Environment variables are commonly used to store secrets: database passwords, API keys, JWT secrets, SMTP credentials. Here is how to handle them safely.

File Permissions

# .env files should only be readable by the owner $ chmod 600 /opt/myapp/.env $ chown myapp:myapp /opt/myapp/.env $ ls -la /opt/myapp/.env -rw------- 1 myapp myapp 256 Mar 17 12:00 /opt/myapp/.env # Systemd env files: readable by root only $ chmod 600 /etc/myapp/env $ chown root:root /etc/myapp/env

Keeping Secrets Out of Git

$ cat .gitignore .env .env.local .env.production *.key *.pem credentials.json
Never commit secrets to git. Even if you delete the file later, it remains in the git history forever. If you accidentally committed a secret: rotate the credential immediately (change the password/key), then use git filter-branch or BFG Repo-Cleaner to purge it from history.

The .env.example Pattern

Ship a .env.example with placeholder values so new developers know what variables are needed without seeing real secrets.

$ cat .env.example APP_ENV=production DB_HOST=localhost DB_PORT=5432 DB_DATABASE=myapp DB_USERNAME=your_db_user DB_PASSWORD=your_db_password JWT_SECRET=generate-a-random-string-here MAIL_HOST=smtp.example.com

Unsetting and Modifying Variables

$ unset MY_VAR # Remove a variable completely $ export -n MY_VAR # Un-export (keep as shell var, stop inheritance) # Append to an existing variable $ export PATH="$PATH:/new/dir" # Check if a variable is set $ [ -z "$MY_VAR" ] && echo "not set" || echo "set to: $MY_VAR"

Environment Variables in Scripts

When writing shell scripts, how variables are handled depends on how you run the script.

Executing a Script

$ ./myscript.sh $ bash myscript.sh

Runs in a child process. Variables set in the script do NOT affect the parent shell. The script inherits exported variables from the parent.

Sourcing a Script

$ source myscript.sh $ . myscript.sh

Runs in the current shell. Variables set in the script ARE visible in the parent shell afterward. This is how .bashrc works.

Practical use case: Create a script that sets up your development environment: source ~/env-project-x.sh. This can set DATABASE_URL, API keys, and PATH modifications that last for the session.

Debugging Environment Issues

When an application cannot find a variable, use these techniques to track down the problem.

1
Check if the variable exists in the current shell: echo $MY_VAR
2
Check if it is exported: export -p | grep MY_VAR
3
Check what a process actually sees: cat /proc/PID/environ | tr '\0' '\n' | grep MY_VAR
4
Check systemd service environment: systemctl show myapp --property=Environment
# See the exact environment of a running process $ cat /proc/$(pgrep -f myapp)/environ | tr '\0' '\n' PATH=/usr/local/bin:/usr/bin HOME=/opt/myapp DATABASE_URL=postgresql://... JWT_SECRET=abc123

Best Practices Summary

Define scope
User? System? Service?
Choose location
.bashrc, /etc/environment, EnvironmentFile
Set permissions
chmod 600 for secrets
Verify
echo, env, /proc/PID/environ
  • Use export for variables that child processes need to see
  • Put per-user variables in ~/.bashrc for broadest compatibility
  • Put system-wide variables in /etc/environment or /etc/profile.d/
  • Use EnvironmentFile in systemd for service-specific configuration
  • Never commit .env files to git — use .env.example as a template
  • Set chmod 600 on any file containing secrets
  • Prefer .env files or EnvironmentFile over hardcoded export statements in profiles
  • Use /proc/PID/environ to debug what a running process actually sees

Environment variables may seem like a basic topic, but getting them right is the difference between a well-configured server and hours of debugging mysterious application failures. Master the concepts in this guide and you will handle configuration issues with confidence across any Linux system.

Share:
See the Demo