How to Use Environment Variables for JWT Secret

If your JWT secret key is sitting inside your code right now, this post is for you. Here's the right way to handle it — takes about 10 minutes to fix.

There's a moment every developer has. You're moving fast, you need a JWT secret, and you just... type it straight into the code.

const token = jwt.sign({ userId: user.id }, "myjwtsecret123");

Feels fine in the moment. Then you push to GitHub and immediately feel a little sick.

Environment variables fix this completely. And once you set them up once, you'll do it automatically on every project going forward.

What Even Is an Environment Variable?

An environment variable is just a value that lives on the machine running your app — not inside your code.

Think of your code as a recipe. The recipe says "add the secret ingredient." The environment variable is where you actually keep that ingredient. The recipe never contains the ingredient itself, just a reference to where it is.

Your code says process.env.JWT_SECRET. The actual secret lives in a separate file that never gets committed to Git. Clean separation.

Step 1: Create Your .env File

In the root folder of your project — same level as your package.json — create a file called exactly .env. No extension, just .env.

Inside it, add your JWT secret like this:

JWT_SECRET=paste_your_generated_key_here

Before you type anything in there, go generate a proper key. I use jwtsecretkeygenerator.com — click the button, copy the key, paste it straight in. Don't type one manually. The whole point is to get a key that's genuinely random, not something your fingers made up.

If you need separate access and refresh token secrets, add both:

JWT_ACCESS_SECRET=your_access_token_secret_here JWT_REFRESH_SECRET=your_refresh_token_secret_here NODE_ENV=development PORT=3000

You can keep other config values in here too. Database URLs, API keys, port numbers — anything that changes between environments or shouldn't be in your code.

Step 2: Add .env to .gitignore RIGHT NOW

This step is non-negotiable. Do it before anything else.

Open your .gitignore file (or create one if you don't have it) and add:

# Environment variables - NEVER commit these .env .env.local .env.production .env.*

The wildcard .env.* catches any variation you might create later. One line, total protection.

Check this worked by running:

git status

Your .env file should not appear in the output. If it does, your .gitignore isn't working — stop and fix it before continuing.

Step 3: Install dotenv

The dotenv package reads your .env file and loads everything into process.env when your app starts.

npm install dotenv

Then at the very top of your main entry file — usually index.js, server.js, or app.js — add this as the first line:

require('dotenv').config();

It needs to be first. Before any other imports that might use environment variables. If it loads after something tries to read process.env.JWT_SECRET, that variable will be undefined and nothing will work.

Step 4: Use It in Your Code

Now replace every hardcoded secret with process.env.YOUR_VARIABLE_NAME:

require('dotenv').config(); const jwt = require('jsonwebtoken'); // Before (bad) // const token = jwt.sign({ userId: user.id }, "myjwtsecret123"); // After (good) const token = jwt.sign( { userId: user.id }, process.env.JWT_SECRET, { expiresIn: '15m' } );

Your code now contains zero secrets. Anyone who reads your code, clones your repo, or browses your GitHub will see process.env.JWT_SECRET and nothing else. The actual value stays on your machine only.

Step 5: Add a Fallback Check on Startup

This one's optional but really useful. Add a check at startup that throws an error if required environment variables are missing:

require('dotenv').config(); // Fail fast if required secrets are missing const requiredEnvVars = ['JWT_SECRET', 'DATABASE_URL']; requiredEnvVars.forEach(varName => { if (!process.env[varName]) { console.error(`❌ Missing required environment variable: ${varName}`); process.exit(1); // Stop the app immediately } }); console.log('✅ All environment variables loaded successfully');

Without this, a missing variable silently becomes undefined. Your JWT signing will fail with a weird error deep in the code and you'll spend 20 minutes figuring out why. This check makes the problem obvious immediately.

What to Do for Production

Your .env file lives on your local machine only. It doesn't go to your server. So how does your production app get the secrets?

Every hosting platform has a way to set environment variables directly. You go into your dashboard, find the environment variables section, and add them manually:

  • Vercel: Project Settings → Environment Variables
  • Railway: Your project → Variables tab
  • Heroku: Settings → Config Vars → Reveal Config Vars
  • Render: Your service → Environment tab
  • DigitalOcean App Platform: App Settings → App-Level Environment Variables
  • VPS / own server: Add to /etc/environment or your systemd service file

The secret goes directly into the platform. Never through your code, never through Git. That's exactly the right flow.

Use Different Values for Dev and Production

Your local .env file and your production environment variables should have different secret values.

Always.

If someone somehow gets your development secret, they can't use it against your production users. And if you need to rotate your production key, you don't have to touch your local setup at all.

Two separate secrets. Two separate environments. Small habit, real protection.

Create a .env.example File for Your Team

If other developers work on your project, they need to know what environment variables exist — just not the actual values.

Create a .env.example file with all the variable names but empty values:

# Copy this file to .env and fill in your values JWT_SECRET= JWT_REFRESH_SECRET= DATABASE_URL= PORT=3000 NODE_ENV=development

Commit this file to Git. It's safe — no real values, just a template. New developers clone the repo, copy .env.example to .env, fill in their values, and they're set up.

It's one of those small things that makes a project feel properly organised.

Common Mistakes to Watch For

These are the ones that catch people out:

  • Forgetting to add require('dotenv').config() as the first line
  • Adding .env to .gitignore after already committing it once
  • Using the same secret values in dev and production
  • Accidentally logging process.env in debug code that makes it into production
  • Spaces around the equals sign in .env (JWT_SECRET = abc won't work — no spaces)
  • Quotes around values when you don't need them (JWT_SECRET="abc" includes the quotes in the value)

The spaces and quotes ones trip people up more than you'd think. Keep your .env file clean and simple:

# Correct JWT_SECRET=a3f8c2d1e7b9044f2c68a5d3e1f7b290 # Wrong - space around equals JWT_SECRET = a3f8c2d1e7b9044f2c68a5d3e1f7b290 # Wrong - unnecessary quotes JWT_SECRET="a3f8c2d1e7b9044f2c68a5d3e1f7b290"

That's Really All There Is to It

Environment variables sound more complicated than they are. The whole setup takes about 10 minutes on a new project.

Create .env, add it to .gitignore, install dotenv, load it first, use process.env in your code. That's the whole thing.

Do it on day one of every project and you'll never have that "I just pushed my secret to GitHub" panic again.

💡 Quick Recap: Generate a proper key → paste into .env → add .env to .gitignorerequire('dotenv').config() first line → use process.env.JWT_SECRET in your code → set the real value directly in your hosting platform for production. Done.