Skip to content
Go back

Where Environment Variables Actually Live in Linux

By SumGuy 5 min read
Where Environment Variables Actually Live in Linux

You set an environment variable. You’re sure you did it right. But then your app doesn’t see it. You log out, log back in. Still doesn’t see it. You set it in one place, but it’s not there in another. You curse Linux and move on.

Here’s the thing: there are five different places where environment variables can live. They’re loaded at different times, in different contexts, with different scopes. Understanding which place to use saves hours of debugging.

The Five Places

1. /etc/environment — System-Wide, All Sessions

This is read at login time by the PAM system. Every user gets these variables.

Terminal window
$ sudo cat /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
LANGUAGE="en_US.UTF-8"
JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64"

Variables here are available to all shells and all users at login. They’re sourced before shell-specific files.

When to use: System-wide settings that apply to every user (e.g., JAVA_HOME, language settings).

Edit it:

Terminal window
$ sudo nano /etc/environment
# Add a new line
MY_APP_HOME="/opt/myapp"
# Save and log out/in

Gotcha: This file is not a shell script. Don’t use variable expansion or complex syntax. Just KEY=value on each line.

2. ~/.profile — Per-User, Login Shells

This is read when you open a login shell (SSH, terminal at boot, su -). Not read in non-login contexts.

Terminal window
$ cat ~/.profile
if [ -n "$BASH_VERSION" ]; then
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
fi

The default ~/.profile usually just sources ~/.bashrc. But you can add variables here:

~/.profile
export MY_DATABASE_URL="postgres://localhost/mydb"
export MY_API_KEY="secret123"

When to use: Per-user settings in login shells. Variables for specific users only.

Gotcha: Not read in non-login shells. Run bash -l to test (the -l makes it a login shell).

3. ~/.bashrc — Per-User, Interactive Shells

This is read every time you open an interactive shell (including non-login shells). Used most often.

~/.bashrc
export PATH="$PATH:$HOME/.local/bin"
export MY_EDITOR="vim"

This runs every shell session, so it’s good for frequently-used variables.

When to use: Personal aliases, functions, path extensions, interactive shell preferences.

Gotcha: Runs every time, so keep it fast. Don’t do slow operations here.

4. ~/.bashrc vs ~/.profile — The Hierarchy

Here’s the actual order:

  1. Login shell (SSH, su -, terminal at boot)

    • Read /etc/profile (system-wide)
    • Read /etc/profile.d/*.sh (system-wide)
    • Read ~/.profile (your login script)
    • Read ~/.bashrc (if ~/.profile sources it, which it usually does)
  2. Non-login shell (bash in an open terminal)

    • Read ~/.bashrc only
  3. Non-interactive shell (script execution)

    • Doesn’t read any of these files
    • Only inherits exported variables from parent

This is why setting a variable in ~/.bashrc might not show up when running a script. Scripts don’t source .bashrc. They only inherit exported variables.

5. Systemd Services — Environment=

For services managed by systemd, use the service file, not shell files:

/etc/systemd/system/myapp.service
[Service]
Type=simple
ExecStart=/usr/bin/myapp
Environment="MY_APP_DEBUG=1"
Environment="DATABASE_URL=postgresql://localhost/mydb"

Or in a drop-in directory:

Terminal window
$ sudo mkdir -p /etc/systemd/system/myapp.service.d
$ sudo nano /etc/systemd/system/myapp.service.d/env.conf
/etc/systemd/system/myapp.service.d/env.conf
[Service]
Environment="MY_VARIABLE=value"

Reload and restart:

Terminal window
$ sudo systemctl daemon-reload
$ sudo systemctl restart myapp

When to use: Environment variables for systemd services only. This is the right way.

The Real-World Scenarios

Scenario 1: “My app doesn’t see the variable”

You set DATABASE_URL in your ~/.bashrc, then run your Node.js app:

Terminal window
$ source ~/.bashrc
$ node app.js
// app doesn't see DATABASE_URL

Why: Your shell sees it, but when node starts, it doesn’t automatically source ~/.bashrc. You need to explicitly export it:

Terminal window
# In ~/.bashrc
export DATABASE_URL="postgres://localhost/mydb"
node app.js

Now node inherits the exported variable.

Scenario 2: “SSH works but cron doesn’t”

You set a variable in ~/.bashrc. Works in SSH. But your cron job doesn’t see it:

Terminal window
$ crontab -e
# Add:
* * * * * /usr/bin/myapp
# myapp doesn't see the variable

Why: Cron doesn’t source ~/.bashrc or ~/.profile. It only inherits variables from the cron environment.

Fix: Set variables in /etc/environment (system-wide) or in the cron job itself:

Terminal window
$ crontab -e
# Set the variable for this job
DATABASE_URL="postgres://localhost/mydb" * * * * * /usr/bin/myapp

Scenario 3: “Systemd service doesn’t see the variable”

You set LOG_LEVEL in ~/.bashrc. The systemd service doesn’t see it:

/etc/systemd/system/myapp.service
[Service]
ExecStart=/usr/bin/myapp
# myapp doesn't see LOG_LEVEL

Why: Systemd services don’t inherit the user’s shell environment. They start fresh.

Fix: Use Environment= in the service file:

/etc/systemd/system/myapp.service
[Service]
ExecStart=/usr/bin/myapp
Environment="LOG_LEVEL=debug"

The Troubleshooting Checklist

Variable not showing up? Follow this:

Terminal window
# 1. Is it exported?
$ echo $MY_VARIABLE
# If empty, it's not set. If set, continue to 2.
# 2. Where is it set?
$ grep -r "MY_VARIABLE" ~/.bashrc ~/.profile /etc/environment
# 3. Did you source it after editing?
$ source ~/.bashrc
$ echo $MY_VARIABLE
# 4. Is it exported?
$ export MY_VARIABLE="value"
# 5. For systemd services, use Environment=

The Rules

For login shells (SSH, remote login): Use /etc/environment (system-wide) or ~/.profile (user-specific).

For interactive shells: Use ~/.bashrc if it’s user-specific and interactive-only.

For scripts run directly:

Terminal window
export MY_VAR="value" && /usr/bin/myscript

For systemd services: Use Environment= in the service file. Never rely on shell files.

For everything else: /etc/environment is the safest default for system-wide settings.

Key Takeaway

Environment variables in Linux are layered. Different contexts load different files. SSH loads profile files. Scripts don’t. Systemd doesn’t care about shells. Understanding which file gets read in which context saves you from hours of “why doesn’t this work?” debugging.

Master this, and you’ll stop cursing environment variables and start debugging actual problems.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it may appear here.


Previous Post
xargs vs while read: Which One and When
Next Post
The Firewall Rule Order That's Breaking Your Setup

Related Posts