Environment Variables in Linux: Configuration That Lives Outside Your Code

Environment variables let you feed settings, paths, and secrets into a program without hard-coding them. Learn what they are, how the shell and processes use them, the difference between shell and exported variables, and how to set them safely on a server — from zero.

Published September 23, 202612 min readBy ACY Partner Indonesia
Environment variables in Linux — key=value settings passed into running programs
300 × 250Ad Space AvailablePlace your ad here

Sooner or later, every program needs to be told things: where to find a file, which database to talk to, what mode to run in, what the secret password is. You could write all of that directly into the code — but the moment you do, you’ve welded your program to one specific setup. Move it to another machine and it breaks. Environment variables are the standard answer to this problem, and on a Linux server you’ll meet them constantly.

The idea is small and the payoff is large. An environment variable is just a named value that lives around a running program rather than inside its source code. Once you understand how they’re set, where they come from, and how a process actually reads them, a huge amount of server configuration stops being mysterious.

What an environment variable actually is

An environment variable is a NAME=value pair that the operating system keeps available to running programs. Every process on a Linux system carries a little dictionary of these pairs with it — its environment. When a program starts, it can look up any name in that dictionary and read the value.

That’s the whole concept. A name, a value, and a process that can read it:

   ENVIRONMENT (a set of name=value pairs)
   ┌─────────────────────────────────────┐
   │ HOME=/home/jane                      │
   │ PATH=/usr/local/bin:/usr/bin:/bin    │
   │ LANG=en_US.UTF-8                     │
   │ APP_ENV=production                   │
   │ DATABASE_URL=postgres://...          │
   └─────────────────────────────────────┘


            a running program
        reads the values it cares about

By convention, environment variable names are written in UPPER_SNAKE_CASE — all capitals, words joined by underscores. That’s not a rule the system enforces, but everyone follows it, so a name like DATABASE_URL instantly reads as “this is an environment variable.”

The value is always a string. There are no numbers, booleans, or lists as far as the environment is concerned — just text. A program that wants a number reads the string "8080" and converts it itself.

Why they exist: separating config from code

The real reason environment variables matter is a principle worth saying out loud: configuration should live outside your code. The same program, byte for byte, should be able to run on your laptop, on a test server, and in production — and behave correctly in each place — without editing a single line. What changes between those places is the configuration, and the environment is where that configuration goes.

Think about a web app that needs a database connection. On your laptop it talks to a local database; in production it talks to a real one with a real password. If the connection string is baked into the code, you’d have to keep two different versions of the program, which is a recipe for shipping the wrong one. Instead, the code reads a variable:

   one program, everywhere
   ─────────────────────────────────────────
   laptop      DATABASE_URL = postgres://localhost/dev
   test        DATABASE_URL = postgres://test-db/app
   production  DATABASE_URL = postgres://prod-db/app

Same code, three environments, zero edits. That’s the payoff. It also keeps secrets — passwords, API keys — out of your source files, which is where they absolutely should not be. A secret committed into code is a secret leaked to everyone who can read the code.

Environment variables are not encrypted

A common misunderstanding: putting a password in an environment variable does not encrypt or hide it. The value is plain text, readable by the process and often by anyone who can inspect that process or read the file you stored it in. Environment variables keep secrets out of your codebase and version control — which is genuinely valuable — but on the server itself you still protect them with file permissions and limited access, the same way you’d protect any other sensitive file.

How a process gets its environment

Here’s the part that ties everything together: a process inherits its environment from whatever started it. When one program launches another, the child receives a copy of the parent’s environment. That copy is independent — the child can change its own variables without affecting the parent.

This inheritance chain is why your shell matters so much. When you log in, a shell starts with an environment. Every command you run from that shell is a child process that inherits a copy of it. So the variables you see in your shell are the variables your commands will see too.

   init / system
        │  (sets a base environment)

   your login shell
        │  (inherits it, may add more)

   the command you run  ← gets a copy


   any program it launches  ← gets a copy of THAT

Because each level gets a copy, a change made deep down never travels back up. A script that sets a variable for itself doesn’t pollute the shell that ran it. This one-way flow is exactly what you want — it keeps things predictable.

Shell variables vs. exported variables

This is the single most common point of confusion, so let’s be precise. Inside a shell there are actually two kinds of variables, and the difference is whether child processes can see them.

A plain shell variable exists only inside the current shell. You set one like this:

GREETING="hello"
echo "$GREETING"   # prints: hello

That works, but GREETING is private to this shell. If you run another program, it won’t see GREETING at all — it was never added to the environment that gets copied to children.

To make a variable part of the environment — so child processes inherit it — you export it:

export GREETING="hello"

Now GREETING is a true environment variable. Any command you run afterward will find it. You can also do it in two steps, or export an already-existing variable:

APP_ENV="production"   # shell variable for now
export APP_ENV         # now it's in the environment

The mental model: a shell variable is a sticky note on your own desk; exporting it pins the note to the wall where everyone walking past can read it.

Set a variable for just one command

You don’t always need a permanent change. You can prefix a single command with a variable, and it applies only to that one run:

APP_ENV=production node server.js

Here APP_ENV is set in the environment of that node process only. The moment the command finishes, it’s gone — your shell never had APP_ENV at all. This is perfect for one-off tasks where you don’t want to leave settings lying around.

Reading and inspecting variables

To read a single variable in the shell, put a $ in front of its name. To see everything currently in your environment, a few commands help:

echo "$HOME"        # print one variable's value
printenv PATH       # print one variable, no $ needed
printenv            # list every exported variable
env                 # same idea — dump the whole environment

printenv and env only show exported variables — the real environment. If you set a plain shell variable and it doesn’t appear in printenv, that’s your clue it wasn’t exported and child programs can’t see it. The set command (in bash) shows shell variables too, which is handy when you’re debugging why something “isn’t there.”

Quoting matters when you read a value. Always wrap $VAR in double quotes — "$VAR" — when you use it. If the value contains spaces and you forget the quotes, the shell splits it into multiple pieces and you get baffling errors. This is one of those habits that saves hours.

The variables you’ll meet everywhere

Linux ships with a set of standard environment variables that almost everything relies on. A few worth recognizing on sight:

  • PATH — a colon-separated list of directories the shell searches when you type a command. When you run git, the shell walks through each directory in PATH until it finds a program named git. If a command is “not found,” a missing PATH entry is often why.
  • HOME — the path to your home directory, like /home/jane. Lots of programs store their config and data relative to HOME.
  • USER — the name of the current user.
  • SHELL — the path to your login shell.
  • LANG / LC_* — language and locale settings (date formats, character encoding, sorting order).
  • PWD — the current working directory, kept up to date as you move around.
  • TZ — the time zone, when set.

PATH is the one you’ll touch most. When you install a tool and the shell can’t find it, the fix is usually to add its directory to PATH:

export PATH="/opt/myapp/bin:$PATH"

Notice the trick: we put the new directory first, then append the old PATH with $PATH. That preserves everything that was already there and just adds to the front.

Making variables stick: temporary vs. permanent

Everything we’ve done so far lasts only as long as the current shell. Close the terminal or log out, and those exports are gone. That’s often fine — sometimes you want a setting to be temporary. But for configuration a server needs every time it starts, you need it to persist.

The traditional way is to put your export lines in a shell startup file that runs each time a shell begins. For an interactive login shell, that’s commonly ~/.bashrc or ~/.profile (the exact file depends on the shell and how it’s launched). Add a line like:

export EDITOR="nano"

…and it’ll be set in every new shell from then on. This is great for your own interactive settings.

For a long-running server process, though, relying on a personal startup file is fragile. A service started by the system at boot doesn’t read your ~/.bashrc — it isn’t a login shell, and it may not even run as you. The cleaner pattern for application configuration is to give the process its environment explicitly: define the variables in the service definition that launches it, or load them from a dedicated config file when the process starts. The principle is the same; you’re just choosing where the NAME=value pairs come from based on who starts the program.

Don't commit your secrets file

A popular convention is to keep configuration in a file (often named .env) listing KEY=value lines, then load it into the environment when the app starts. It’s a fine pattern — with one iron rule: never commit that file to version control. It almost always contains secrets. Add it to your ignore list, keep a safe .env.example with dummy values for documentation, and store the real file only on the machines that need it, protected by tight file permissions. Plenty of breaches trace back to a secrets file accidentally pushed to a public repository.

A concrete walk-through

Let’s tie it together with a tiny session. Imagine Jane is setting up a server process by hand to test it:

# nothing set yet — empty output
printenv APP_ENV

# set it, but only as a shell variable (not exported)
APP_ENV="staging"
printenv APP_ENV          # still empty — children can't see it!

# now export it
export APP_ENV
printenv APP_ENV          # staging

# the app, launched from this shell, inherits APP_ENV
node server.js            # inside the app, APP_ENV reads "staging"

# override it for just one run, without changing the shell
APP_ENV=production node server.js   # this run sees "production"
printenv APP_ENV          # back to "staging" — the override was local

Every line here is one of the ideas from above doing its job: the export boundary, inheritance into the child process, and the one-command override that leaves the shell untouched. Once this little dance feels natural, configuration on a server stops being guesswork.

Why this matters on a server

On your own laptop you might get away with hard-coded settings, because there’s only one of you and one machine. On a server, environment variables become essential. They’re how the same deployable artifact runs in staging and production, how secrets stay out of your codebase, how a process running as a service gets exactly the configuration it needs without anyone editing files by hand. Almost every modern deployment workflow — from a single VPS to a fleet of containers — leans on this one simple NAME=value mechanism.

If you’ve already gotten comfortable in the Linux command line, environment variables are the next layer of control: not just running commands, but shaping the world those commands run in. And because each user on a server gets their own environment, the same variable can hold different values for different users — another reason this mechanism scales so neatly.

Wrapping up

Here’s the whole picture in one place:

  • An environment variable is a NAME=value pair the OS keeps available to running programs; the value is always a string, and names are written in UPPER_SNAKE_CASE by convention.
  • They exist to separate configuration from code — the same program runs everywhere, with only its environment changing — and to keep secrets out of your source files.
  • A process inherits a copy of its parent’s environment; changes flow one way, parent to child, never back.
  • A plain shell variable is private to the current shell; you export it to make it part of the environment that children inherit.
  • Read variables with $NAME, printenv, and env; PATH, HOME, and USER are standard ones you’ll see constantly.
  • Set them temporarily in a shell or per command, or permanently via a startup file or a service’s own configuration — and never commit a secrets file to version control.

Next, it’s worth looking at how you actually reach a server in the first place to set any of this up — securely connecting from your machine to a remote one over SSH, the tool every server administrator lives in.

Tags:linuxenvironment-variablesconfigurationserverbeginner
728 × 90Ad Space AvailablePlace your ad here

Related Articles

See All Articles

You Might Also Like

SSL and encryption at rest — data protected in transit and on disk
Server / Server Security

SSL and Encryption at Rest: Protecting Data Both in Transit and on Disk

Encryption protects your data in two places: while it travels the network (in transit) and while it sits on disk (at rest). Learn the difference, why you need both, how TLS, full-disk encryption, and key management actually fit together, and the practical mistakes to avoid — explained from the.

Nov 9, 202613 min read
Securing ports and services — closing the doors on a server you don't use
Server / Server Security

Securing Ports and Services: Closing the Doors You Don't Use

Every open port on a server is a door someone could try to walk through. Learn what ports and services really are, why an exposed service is your biggest risk, and the simple discipline of closing everything you don't need — explained from the ground up.

Nov 8, 202611 min read
Principle of least privilege — granting only the minimum access each user and process needs
Server / Server Security

The Principle of Least Privilege: Give Everything Only the Access It Truly Needs

Least privilege is the quiet rule behind almost every solid security setup: every user, process, and key gets the minimum access required to do its job, and nothing more. Learn what it means, why it limits the blast radius of any breach, and how to apply it across users, services, files, and keys.

Nov 7, 202613 min read
Fail2ban and intrusion basics — watching logs and automatically banning repeated abusers
Server / Server Security

Fail2ban and Intrusion Basics: Automatically Banning the Bots Hammering Your Server

Attackers don't stop after one failed guess — they keep hammering, thousands of times a day. Learn how intrusion attempts actually look, what fail2ban does, how it watches your logs and bans abusers automatically, and how to set it up sensibly without locking yourself out — explained from zero.

Nov 6, 202613 min read
Keeping software updated — closing known security holes before attackers use them
Server / Server Security

Keeping Software Updated: The Boring Habit That Stops Most Server Breaches

Most servers don't get hacked through clever new attacks — they get hacked through old, known holes that a simple update would have closed. Learn why updates matter, what to update, how to do it safely without breaking things, and how to make it a habit instead of a panic.

Nov 5, 202610 min read
Firewall configuration — a default-deny rule set that opens only the ports you need
Server / Server Security

Firewall Configuration: Setting Up Default-Deny Rules Without Locking Yourself Out

Knowing what a firewall is and actually configuring one well are two different skills. Learn how to write a default-deny rule set, order rules correctly, allow your own access first, handle IPv6 and cloud security groups, and test before you commit — a practical, vendor-neutral guide from zero.

Nov 4, 202615 min read