Skip to content
Skip to main content
A vintage wooden key cabinet with its glass door half open on a dusty-rose plaster wall, rows of brass keys with blank tags around one oversized iron master key, a metaphor for n8n's $env access block and the N8N_ENCRYPTION_KEY it protects
8 min readBy Carlos Aragon

Fix n8n "access to env vars denied" in n8n 2.x

n8n 2.x throws "access to env vars denied" because $env is blocked by default, and n8n only lifts the block when N8N_BLOCK_ENV_ACCESS_IN_NODE is exactly the lowercase string false. Not False, not 0, not "false"with quotes that Docker Compose quietly kept. I booted n8n 2.44.6 a dozen times today to prove it. There's a catch, too. The moment it works, every Code node can read your encryption key.

Why Does n8n 2.x Say "access to env vars denied"?

Because the default flipped in 2.0. The n8n 2.0 breaking changes page says it plainly: "The default value for N8N_BLOCK_ENV_ACCESS_IN_NODE is now set to true." The security environment variables page still lists the default as falseas I write this. If you checked that table to decide whether you needed the variable, you got the wrong answer. That's probably how half the forum threads on this error started.

I stopped trusting either page and read the code that ships in the n8nio/n8n:2.44.6 image, the current stable tag:

// n8n-workflow/dist/cjs/workflow-data-proxy-env-provider.js (n8n 2.44.6)
const isEnvAccessBlocked = isProcessAvailable
    ? process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE !== 'false'
    : false;
const env = !isProcessAvailable || isEnvAccessBlocked ? {} : process.env;

Read the comparison carefully. It doesn't parse a boolean. It checks whether the string is anything other than 'false'. Unset counts as blocked. So do true, FALSE, no, 0, and any value with a stray quote or space.

Takeaway: on n8n 2.x, if you haven't explicitly set the variable to false, $env is off.

Which N8N_BLOCK_ENV_ACCESS_IN_NODE Values Actually Work?

Reading source is one thing. I wanted executions. The setup: a fresh n8n 2.44.6 container per value, a published workflow with a production webhook, and three probes in a row: a Code node, an Edit Fields (Set) node with {{ $env.MY_APP_URL }}, and an HTTP Request node using a Header Auth credential whose value was the same expression. Every probe node had "continue on error" on, so one curl got me all three answers.

Value$envNote
(not set)Blockedthe 2.x default
trueBlockedas expected
FalseBlockedcase-sensitive check
0Blockednot parsed as a boolean
"false" (literal quotes)Blockedwhat Compose list syntax sends
"false " (trailing space)Blockedcopy-paste from a doc
falseAllowedthe only value that works

The blocked runs failed identically in all three places. The Code node and the Set node both said access to env vars denied. The credential said access to env vars denied [Error resolving credentials], which is the one that sends people hunting for a broken API key. There's no separate switch for credentials. It's the same flag.

I also tried N8N_EXPRESSIONS_ALLOWED_ENV_VARS=MY_APP_URL, because a forum answer recommends it as the "safer" allowlist. It doesn't exist. Not in the source, not in behavior: the run came back blocked, same as unset. In 2.44.6, $env is all or nothing.

Takeaway: one value works, false, and it opens Code nodes, expressions and credentials together.

Why Is It Still Blocked After I Set It in docker-compose?

Quotes. This is the one I'd bet on if you swear you already set it. Docker Compose treats the two environment: syntaxes differently, and plain docker run --env-file disagrees with Compose's own env_file. I ran each form through Compose v5.0.2 and printed the value with brackets around it from inside the container:

How you set itWhat n8n seesResult
- N8N_BLOCK_ENV_ACCESS_IN_NODE="false"["false"]Blocked
- N8N_BLOCK_ENV_ACCESS_IN_NODE=false[false]Works
N8N_BLOCK_ENV_ACCESS_IN_NODE: "false"[false]Works
N8N_BLOCK_ENV_ACCESS_IN_NODE: false (YAML bool)[false]Works
env_file: with VAR="false"[false]Works
docker run --env-file with VAR="false"["false"]Blocked

The list form with quotes looks completely normal in a YAML file. Compose passes the quote characters straight into the container, n8n compares "false" to false, and the block stays on. Same story with docker run --env-fileand a quoted line, while Compose's env_file: strips the same quotes. So a .env file can work in one setup and fail in the other. Check what actually arrived:

docker exec n8n sh -c 'printf "[%s]\n" "$N8N_BLOCK_ENV_ACCESS_IN_NODE"'
# [false]      -> good
# ["false"]    -> Compose list quotes, still blocked

One more trap: the editor. When you hover an expression in the canvas, the preview runs in your browser, where there is no process. The source returns not accessible via UI, please run node there no matter how the server is configured. Judge the fix by a real execution, not the preview.

What Does Setting It to false Expose?

Everything in the main process environment. Look at the source again: const env = ... process.env. There's no filter. Since 2.x, Code nodes run in a separate task runner process, so I expected that to limit the damage. It doesn't. The runner's own environment is a short allowlist, but n8n serializes the main process env into each task. This probe ran in the Code node with the flag at false:

const out = {};
try { out.appUrl = $env.MY_APP_URL ?? null } catch (e) { out.appUrl = 'ERR: ' + e.message }
try {
  const k = $env.N8N_ENCRYPTION_KEY;
  out.encKey = k ? `present (${k.length} chars)` : 'absent';
} catch (e) { out.encKey = 'ERR: ' + e.message }
try { out.process = typeof process } catch (e) { out.process = 'ERR: ' + e.message }
return [{ json: out }];
{"appUrl":"https://app.example.com",
 "encKey":"present (23 chars)",
 "dbPass":"present",
 "process":"undefined"}

N8N_ENCRYPTION_KEY is the key that decrypts every credential stored in the database, and anyone who can edit a workflow just got it in one line. process itself stays undefined in the sandbox in every configuration, so process.envcode from 1.x won't work either way. $env is the only door, and it opens all the way.

That's why I don't treat this as a one-line fix. On a single-owner box, fine. On an instance where a client or a junior can edit workflows, flipping it to false hands them the master key. The same logic applies to what you let into execution logs, which I covered in n8n execution data redaction.

Takeaway: falsedoesn't expose "your app variables." It exposes the whole environment, secrets included.

How Do I Unblock $env Without Leaking the Encryption Key?

Since you can't allowlist variables, control what's in the environment. n8n reads many config variables from a file when you append _FILE to the name. I booted a hardened run with N8N_ENCRYPTION_KEY_FILE=/run/secrets/n8n_key, no DB_POSTGRESDB_PASSWORD in the env, and the flag at false:

  • $env.MY_APP_URL worked in the Code node, the Set node and the credential.
  • $env.N8N_ENCRYPTION_KEY came back absent. The credential still decrypted fine, so n8n was using the file.
  • $env.N8N_ENCRYPTION_KEY_FILE showed the path. So I pointed a Read/Write Files from Disk node at /run/secrets/n8n_key. It answered Access to the file is not allowed. The 2.x file-access restriction covered it.

This is the Compose shape I'd ship:

services:
  n8n:
    image: n8nio/n8n:2.44.6
    environment:
      N8N_BLOCK_ENV_ACCESS_IN_NODE: "false"
      N8N_ENCRYPTION_KEY_FILE: /run/secrets/n8n_key
      DB_POSTGRESDB_PASSWORD_FILE: /run/secrets/db_pass
      MY_APP_URL: https://app.example.com
    secrets: [n8n_key, db_pass]

secrets:
  n8n_key:
    file: ./secrets/n8n_key
  db_pass:
    file: ./secrets/db_pass

Map syntax, so no quote surprises. Secrets as files. Only plain config, like base URLs and feature flags, as real env vars. And if a value is a secret, it doesn't belong in $env at all. Put it in a credential, where n8n encrypts it and keeps it out of expressions. If you're on Business or Enterprise self-hosted (Pro or Enterprise on Cloud), custom variables through $vars give you shared config without touching the flag. On the community edition, a single Edit Fields node at the top of the workflow does the same job for a handful of values.

Will the Migration Report Warn Me About $env?

Don't count on it. The 2.0 rule for this change, process-env-access-v2, only scans for the pattern process.env in node parameters. A workflow that uses $env.API_BASEwon't match. The rule also skips itself completely when the variable is set to any value, including "false" with the quotes that keep you blocked.

If you're planning the jump, grep your exported workflows for $env yourself. My n8n 3.0 breaking changes checklist and the Python Code node import guide cover the other sandbox defaults that changed underneath old workflows. If you're still deciding whether to self-host at all, this is exactly the kind of setting that comes with it. See n8n Cloud vs self-hosted.

Takeaway: the built-in checker misses $env. Search for it before upgrading.

n8n Environment Variables FAQ

Why does n8n say access to env vars denied?

Since n8n 2.0, $env is blocked by default. n8n unblocks it only when N8N_BLOCK_ENV_ACCESS_IN_NODE is exactly the lowercase string false. Unset, true, False, 0 or a quoted value all keep it blocked. I confirmed this on n8n 2.44.6.

I set N8N_BLOCK_ENV_ACCESS_IN_NODE=false and it still fails. Why?

The value probably isn't the bare string false. Docker Compose list syntax like - N8N_BLOCK_ENV_ACCESS_IN_NODE="false" passes the quotes through, and so does docker run --env-file. Print the variable inside the container and look for quotes or trailing spaces.

Does N8N_BLOCK_ENV_ACCESS_IN_NODE also block credentials?

Yes. A Header Auth credential whose value was {{ $env.MY_APP_URL }} failed with 'access to env vars denied [Error resolving credentials]' until the variable was set to false. The same flag covers Code nodes, node expressions and credential expressions.

Is it safe to set N8N_BLOCK_ENV_ACCESS_IN_NODE to false?

Only if you trust everyone who can edit workflows. With it set to false, a Code node read N8N_ENCRYPTION_KEY and DB_POSTGRESDB_PASSWORD in my test. Move those secrets to the _FILE variants first, so they never appear in $env.

Can I allow only specific environment variables in n8n?

Not in n8n 2.44.6. N8N_EXPRESSIONS_ALLOWED_ENV_VARS, which shows up in forum answers, doesn't exist in the source and changed nothing when I set it. Access is all or nothing, so control what's in the environment instead.

How I Tested This

Docker image n8nio/n8n:2.44.6 (the stable tag on October 24, 2026), SQLite, default internal task runner, one fresh container per configuration. Workflow and credential imported with the n8n CLI, published, then triggered through its production webhook. Compose checks used Docker Compose v5.0.2. The encryption keys and database password in the test were throwaway values. I printed only whether they were present and their length, never the values.

Self-Hosting n8n for a Team or Clients?

I set up and harden self-hosted n8n: secrets out of the environment, upgrades tested before they hit production, workflows that don't break on the next default change. Tell me what you're running.

Related Posts