Back to Tools

dotenv Not Loading Your .env Variables — 7 Fixes

Covers CommonJS, ES modules, TypeScript, Next.js, and Docker

dotenv silently fails more often than it throws an error. When your process.env variables are still undefined after calling dotenv.config(), one of these 7 issues is almost always the cause.

Fix 1: require('dotenv').config() Must Be Line 1

This is the most common cause. ES module import statements are hoisted — meaning they execute before any code you write. If you import other modules before dotenv loads, those modules will not see the env variables.

Broken — imports hoist above dotenv:

import { connectDB } from './db';   // ← runs first, before dotenv
import dotenv from 'dotenv';
dotenv.config();                     // ❌ too late — db.js already imported

Fixed — dotenv as first import:

import 'dotenv/config';             // ✅ line 1 — executes first

import { connectDB } from './db';   // now has access to process.env

Fix 2: .env File Must Be in the Project Root

dotenv resolves the .env path relative to process.cwd() — the directory from which you run the node command. This is usually your project root.

Wrong — .env inside src/:

my-project/
├── src/
│   ├── .env        ❌ dotenv will not find this
│   └── index.js
└── package.json

# Even if you run: node src/index.js
# process.cwd() is still /my-project — not /my-project/src

Correct — .env in root:

my-project/
├── .env            ✅ root — matches process.cwd()
├── src/
│   └── index.js
└── package.json

Fix 3: Use the path Option for Custom Location

If you need to keep your .env file in a non-standard location, use the path option to tell dotenv exactly where to look.

Use path option for custom .env location:

const path = require('path');
require('dotenv').config({
  path: path.resolve(__dirname, '../config/.env') // ✅ absolute path
});

// Or relative to current file:
require('dotenv').config({
  path: path.join(__dirname, '.env') // always resolves from this file's dir
});

// Multiple .env files (dotenv v16+):
require('dotenv').config({ path: ['.env.local', '.env'] });

Always use path.resolve() or path.join() — never rely on bare relative strings with dotenv.

Fix 4: ESM / TypeScript — Use import Differently

CommonJS (require) and ES modules (import) need different dotenv loading patterns.

ES Module (type: "module" in package.json):

// Option A: side-effect import (recommended)
import 'dotenv/config';  // ✅ first line

// Option B: explicit config call
import { config } from 'dotenv';
config();

// Option C: CLI flag (no code changes needed)
// node --require dotenv/config src/server.js

TypeScript with ts-node:

// src/index.ts — line 1:
import 'dotenv/config';

// Or via CLI:
// ts-node -r dotenv/config src/index.ts

// package.json script:
{
  "scripts": {
    "dev": "ts-node -r dotenv/config src/index.ts"
  }
}

Fix 5: Check for Typos — Use dotenv Debug Mode

dotenv silently ignores missing files. Enable debug: true to see exactly what dotenv finds — and where it is looking.

Enable dotenv debug mode:

require('dotenv').config({ debug: true });

// Console output will show:
// [dotenv][DEBUG] Trying to load /app/.env
// [dotenv][DEBUG] Loaded /app/.env
// [dotenv][DEBUG] "DB_HOST" is already defined in process.env and will not be overwritten

// If file is missing:
// [dotenv][DEBUG] Failed to load /app/.env: ENOENT: no such file or directory

The debug output tells you the exact path dotenv resolved — use this to confirm your file is where dotenv expects it.

Fix 6: .env.local vs .env — Next.js Priority Order

Next.js supports multiple .env files with a strict priority order. Understanding this prevents unexpected variable values.

Next.js .env file priority (highest to lowest):

# 1. Process environment (system/shell vars) — always wins
# 2. .env.local          — local overrides, gitignored, highest .env priority
# 3. .env.development    — loaded when NODE_ENV=development
# 3. .env.production     — loaded when NODE_ENV=production
# 3. .env.test           — loaded when NODE_ENV=test
# 4. .env                — base defaults, committed to git

# Recommended usage:
# .env          → default values, safe to commit (no secrets)
# .env.local    → your local secrets (add to .gitignore)
# .env.production → production-specific non-secret values

For plain Node.js with dotenv (not Next.js), none of this hierarchy applies — dotenv only loads the one file you specify.

Fix 7: Docker / CI — System Variables Override .env

By default, dotenv will not overwrite variables already set in process.env. In Docker or CI systems, variables are often injected by the platform — making dotenv appear to do nothing.

dotenv skips variables already in environment:

# Docker run command sets DB_URL:
# docker run -e DB_URL=prod-db.example.com my-app

# .env file:
# DB_URL=localhost

require('dotenv').config();
console.log(process.env.DB_URL); // "prod-db.example.com" — .env ignored!

Force .env values with override: true:

require('dotenv').config({ override: true }); // ✅ .env wins over system vars

// Or conditionally — only override in development:
if (process.env.NODE_ENV !== 'production') {
  require('dotenv').config({ override: true });
}

Verify dotenv is Working

Use this debug script to quickly confirm dotenv is loading correctly before adding more code:

debug-env.js — run with: node debug-env.js

// debug-env.js
const result = require('dotenv').config({ debug: true });

if (result.error) {
  console.error('dotenv ERROR:', result.error.message);
  process.exit(1);
}

console.log('dotenv loaded successfully.');
console.log('Parsed variables:');

for (const [key, value] of Object.entries(result.parsed || {})) {
  // Mask secrets — show only the first 4 chars
  const masked = value.length > 4 ? value.slice(0, 4) + '****' : '****';
  console.log(`  ${key} = ${masked}`);
}

console.log('\nChecking specific variables:');
const required = ['DATABASE_URL', 'API_KEY', 'PORT'];
for (const key of required) {
  const status = process.env[key] ? '✅' : '❌ MISSING';
  console.log(`  ${key}: ${status}`);
}

Validate your JSON config files →

Paste your JSON config and validate it against a schema — free, no signup.

Frequently Asked Questions

Why is dotenv not loading my .env file?

Most likely: the call is not on line 1, the .env is not in the project root, or a system variable is shadowing it. Enable debug: true to diagnose.

How do I use dotenv with ES modules?

Use import 'dotenv/config' as your first import, or start node with --require dotenv/config.

How do I use dotenv with TypeScript?

Add import 'dotenv/config' as the first line in your TypeScript entry file. For ts-node, use ts-node -r dotenv/config src/index.ts.

What is the difference between .env and .env.local?

In Next.js, .env.local overrides .env and is gitignored. For plain Node.js with dotenv, only the file you specify is loaded — the hierarchy is a Next.js feature.

Why does dotenv not work in Docker or CI?

System-injected variables take precedence. Use dotenv.config({ override: true }) to force .env values to win over system variables.

Related Tools