\ \ \ \ \ Environment Variables Not Working — Why Your .env File Isn't Loading in Production\ \ \ \ \ \ \ \
\

MeatButton

\ \
\ \
\

Environment Variables Not Working: Why Your .env File Isn't Loading in Production

\
For anyone whose app can't find its API keys or config values
\ \

Your app runs perfectly on your laptop. You deploy it. It crashes, or it starts but something is wrong — Stripe payments fail, the database won't connect, login doesn't work, or you see \"undefined\" where your API key should be.

\ \

The problem is almost always environment variables. They exist on your computer. They don't exist on your server. And nobody told you that deploying your code doesn't automatically deploy your configuration.

\ \

What are environment variables, in plain English?

\ \

Environment variables are configuration values that your app needs to run. Things like:

\ \ \ \

These values are stored outside your code on purpose. You don't want your Stripe secret key sitting in a file that gets pushed to GitHub for the world to see. So instead, you put it in a .env file or set it in your server's environment, and your app reads it from there at startup.

\ \

The .env file usually looks something like this:

\ \
DATABASE_URL=postgres://user:password@localhost:5432/mydb\
STRIPE_SECRET_KEY=sk_live_abc123...\
SESSION_SECRET=some-long-random-string\
NODE_ENV=production
\ \

Simple enough. The problem is getting those values to actually exist in every place your app runs.

\ \

The \"works locally, broken in production\" problem

\ \

Here's what happens. You're building your app on your laptop. You create a .env file, put your keys in it, and everything works. Your app reads the file, finds the values, connects to the database, talks to Stripe, life is good.

\ \

Then you deploy. You push your code to a server — a VPS, a Docker container, Vercel, Railway, whatever. The code goes up. But the .env file doesn't.

\ \

Why? Because your .gitignore file (correctly) tells git to never commit the .env file. It contains secrets. It should never go into your repository. But that means when you git pull on your server, the .env file isn't there. Your app starts up, tries to read process.env.DATABASE_URL, gets undefined, and either crashes immediately or runs in a broken state where nothing works right.

\ \

This is the single most common deployment problem that exists. It happens to beginners and experienced developers alike. And there are about eight different ways it can go wrong depending on how you deploy.

\ \

Every way environment variables can fail

\ \

1. The .env file isn't on your server

\ \

This is the most basic version. You have a .env file on your laptop. You deployed your code with git or a CI pipeline. The .env file is in .gitignore, so it never made it to the server. Your app starts up and every single environment variable is undefined.

\ \

Fix: You need to create the .env file on your server manually. SSH in, navigate to your app's directory, and create the file:

\
nano /home/deploy/myapp/.env
\

Paste in your production values (which might be different from your development ones — different database, different API keys, etc.).

\ \

2. The .env file is in the wrong directory

\ \

The file exists on the server, but it's not where your app is looking for it. Most tools (like the dotenv npm package) look for the .env file in the current working directory — whatever folder the app was started from. If your app is in /home/deploy/myapp but you put the .env file in /home/deploy, it won't be found.

\ \

This also happens with systemd services, where the working directory might not be what you expect (see below).

\ \

Fix: Put the .env file in the same directory as your app's main entry point, or configure your tool to look in a specific path.

\ \

3. The dotenv package isn't loading the file

\ \

In Node.js, the .env file doesn't load itself. You need a package — usually dotenv — and you need to actually call it at the very top of your app before anything else tries to use the variables:

\ \
require('dotenv').config()  // must be the FIRST thing
\ \

If this line is missing, or if it runs after other code has already tried to read process.env.DATABASE_URL, those reads will get undefined. In Python, the equivalent is python-dotenv with load_dotenv().

\ \

There's a subtle version of this: your app works in development because your IDE or dev tool loads the .env file automatically. In production, no IDE is running — the file just sits there unread.

\ \

Fix: Make sure your dotenv call is the very first line that runs in your application. Not after imports that use env vars. Not in a config file that gets loaded second. First.

\ \

4. Systemd doesn't read .env files

\ \

If your app runs as a systemd service on a Linux server, this is a big one. Systemd runs your app in a completely bare environment. It doesn't read your .bashrc, it doesn't load your .env file, and it doesn't inherit any environment variables from your shell session.

\ \

Your app worked when you ran it manually in your terminal because your terminal session had all the variables loaded. Systemd has none of them.

\ \

Fix: Add environment variables to your service file:

\
[Service]\
# Option 1: Point to your .env file\
EnvironmentFile=/home/deploy/myapp/.env\
\
# Option 2: Set them individually\
Environment=DATABASE_URL=postgres://user:pass@localhost/mydb\
Environment=NODE_ENV=production
\ \

Important: if you use EnvironmentFile, the file format must be plain KEY=value lines. No export keyword. No surrounding quotes on the value. No inline comments. Systemd is picky about this.

\ \

After changing the service file, always run:

\
sudo systemctl daemon-reload\
sudo systemctl restart myapp
\ \

5. Docker needs env vars passed explicitly

\ \

Docker containers are isolated boxes. They don't inherit environment variables from the host machine and they don't automatically read .env files. You have to pass variables in explicitly.

\ \

Fix: There are several ways:

\
# Pass individual variables\
docker run -e DATABASE_URL=postgres://... -e NODE_ENV=production myapp\
\
# Pass a whole .env file\
docker run --env-file .env myapp
\ \

If you're using Docker Compose, add it to your docker-compose.yml:

\
services:\
  myapp:\
    image: myapp\
    env_file:\
      - .env\
    # or individually:\
    environment:\
      - DATABASE_URL=postgres://...\
      - NODE_ENV=production
\ \

Note: the .env file path in --env-file or env_file: is relative to where you run the command, not relative to anything inside the container.

\ \

6. You set the variable but didn't export it

\ \

This one is subtle and catches people who set variables manually on their server. There's a difference between these two commands:

\ \
# This sets the variable in your current shell only\
DATABASE_URL=postgres://user:pass@localhost/mydb\
\
# This sets it AND makes it available to programs you launch\
export DATABASE_URL=postgres://user:pass@localhost/mydb
\ \

Without export, the variable exists in your shell, but when you run node server.js, the Node process doesn't inherit it. Your app sees undefined.

\ \

Fix: Always use export when setting variables in the terminal. Or better yet, put them in a .env file and use one of the methods above.

\ \

7. Platform dashboards vs. local .env files

\ \

If you deploy to Vercel, Netlify, Railway, Render, or similar platforms, they have their own way of handling environment variables — through a dashboard in their web UI. Your local .env file is irrelevant. It never gets uploaded. The platform has no idea it exists.

\ \

You need to go into your project's settings on the platform, find the \"Environment Variables\" section, and add each variable there manually.

\ \

Common gotcha: you add the variables to the dashboard but they don't take effect. Most platforms require a redeploy after changing environment variables. The variables are baked in at build time, not read live.

\ \

Fix: Add your variables in the platform's dashboard. Redeploy. Check that you're adding them to the right environment (many platforms separate \"Production,\" \"Preview,\" and \"Development\").

\ \

8. Typos and invisible characters

\ \

You'd be surprised how often the variable is technically set, but it's wrong. A trailing space in the value. A smart quote instead of a straight quote (happens when you copy from Slack or a document). The key is STRIPE_SECRET_KEY in the .env file but your app reads STRIPE_SECRET. One character off and the whole thing breaks silently.

\ \

Fix: Copy the exact variable names from your code. Don't retype them. And use printenv on your server to verify what's actually set (see debugging section below).

\ \

How to debug environment variable problems

\ \

Before you change anything, figure out what's actually happening. Here are the quickest ways to check.

\ \

Check if a variable is set on your server

\
# See if a specific variable exists and what its value is\
printenv | grep DATABASE_URL\
\
# See ALL environment variables (can be a long list)\
printenv
\ \

If the variable doesn't appear, it's not set in the current environment. If it appears but the value is wrong, now you know what to fix.

\ \

Check your app's startup logs

\

Most frameworks print useful information when they start up. Look for messages like:

\ \ \

For systemd services, check the logs with:

\
journalctl -u myapp -e --no-pager
\ \

For Docker containers:

\
docker logs my-container-name --tail 50
\ \

Add a temporary debug check

\

If you're really stuck, add a few lines at the very top of your app that print whether key variables exist:

\ \
// Node.js — put this at the very top of your entry file\
console.log('DATABASE_URL set:', !!process.env.DATABASE_URL);\
console.log('STRIPE_SECRET_KEY set:', !!process.env.STRIPE_SECRET_KEY);\
console.log('NODE_ENV:', process.env.NODE_ENV);
\ \

This prints true or false for each variable — enough to tell you what's missing without printing the actual secret values to the logs. Remove these lines before going live. They're a diagnostic tool, not a permanent fixture.

\ \

The .env.example pattern

\ \

Here's a practice that prevents this whole mess from happening again. Keep two files:

\ \ \ \
# .env.example — commit this to your repo\
DATABASE_URL=postgres://user:password@localhost:5432/mydb\
STRIPE_SECRET_KEY=sk_test_replace_me\
SESSION_SECRET=replace-with-a-random-string\
NODE_ENV=production
\ \

When you deploy to a new server (or a teammate clones the repo), they copy .env.example to .env and fill in the real values. No guessing which variables are needed. No scrolling through code to figure out what's missing.

\ \

Why AI is bad at fixing this

\ \

AI can generate a perfect .env file. It can write dotenv configuration. It can even write a systemd service file with EnvironmentFile= in all the right places. The problem is that AI doesn't know how you deploy.

\ \

Are you pushing to a VPS with git? Are you using Docker? Docker Compose? Vercel? Railway? A bare EC2 instance? Each one handles environment variables differently. The .env file that works with dotenv in Node.js doesn't work with systemd's EnvironmentFile because the format rules are different. The variables you set in Vercel's dashboard don't exist when you build locally. Docker ignores everything outside the container unless you explicitly pass it in.

\ \

AI doesn't ask about any of this. It sees \"environment variables not working\" and gives you a generic answer — usually \"make sure you have a .env file and dotenv installed.\" That's correct for local development. It's useless if your app runs in a Docker container on a VPS behind systemd, and the .env file exists but in the wrong directory, with the wrong format, being read by the wrong tool.

\ \

The other AI trap: it generates a .env file with placeholder values and never tells you that you need to get that file onto your server. It assumes you know the deployment part. But the deployment part is the entire problem.

\ \

Environment variable issues are configuration problems, not code problems. AI is good at code. Configuration requires knowing your specific setup — what's running where, how it was installed, what tools manage it. AI can't see any of that.

\ \
\

Still can't figure out why your variables aren't loading?

\

MeatButton connects you with a real expert who can look at your actual deployment, check your actual server, and find exactly where the chain breaks. No more guessing. First one's free.

\ Get MeatButton\
\ \
\

Related articles

\ \
\
\ \ \ \ \