LINE予約「LUCA」の機能と料金はこちら

n8nのCodeノードで「access to env vars denied」が出る原因と直し方

n8n 実務ノート 公開 更新

n8nのCodeノードで $env.OPENAI_API_KEY のように環境変数を読むと、「access to env vars denied」で実行が止まることがあります。コードに誤りがなくても出ます。キーの値を見直しても、原因は見つかりません。

この記事の結論

  • n8nが、式やCodeノードから環境変数を読ませない設定のときに出るエラーです。コードの誤りではありません。
  • n8n 2.0から、既定は拒否です。許可するには、ワークフローを実行するn8nのプロセスに N8N_BLOCK_ENV_ACCESS_IN_NODE=false を渡して再起動します。キューモードなら、workerにも渡します。
  • 許可されるのは、値が小文字の false と完全に一致したときだけです。変数を消しても、拒否のままです。
  • 許可すると、ワークフローを編集できる人は環境変数を読めます。起動の設定を変えられない環境では、CredentialsやVariablesへ移します。

実際に起きたこと

弊社は2026年5月5日に、ワークフローのCodeノードに直接書いていたOpenAIのAPIキーを、$env.OPENAI_API_KEY で読む形に置き換えました。

2026年5月6日に、$env を読むワークフローが次のエラーで止まりました。

Error: access to env vars denied

実行のたびに同じエラーになり、エラーの記録が5分おきに積み上がりました。キーの値が間違っていたのではありません。キーを読む手前で、n8nが読み取りを断っていました。

n8nの起動時に N8N_BLOCK_ENV_ACCESS_IN_NODE=false を渡し、再起動して直りました。この件をきっかけに、ワークフローを作る前と直す前の点検の手順を作りました。その中に、Codeノードに $env. があれば、この変数がn8nのプロセスに入っているかを確かめる項目があります。

2026年7月12日には、ワークフローに平文で書いていたAPIキーを、すべて $env の参照へ移しました(88本・247箇所)。キーを差し替えるときに、ワークフローを1本ずつ直さずに済むからです。そのぶん、N8N_BLOCK_ENV_ACCESS_IN_NODE=false が外れると、$env を読むワークフローがまとめて止まります。

原因

エラーを投げているのは、$envを読むときの判定

$env は、式({{ }} で書く値)やCodeノードから、n8nの環境変数を読むための変数です。どちらの $env も、n8n-workflowパッケージの createEnvProvider を通ります。手元の2.19.2のソースは次のとおりです。

function createEnvProviderState() {
    const isProcessAvailable = typeof process !== 'undefined';
    const isEnvAccessBlocked = isProcessAvailable
        ? process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE !== 'false'
        : false;
    const env = !isProcessAvailable || isEnvAccessBlocked ? {} : process.env;
    // ...(中略)...
}

// $env から名前を読むとき(get の中から抜粋)
// ...(中略)...
if (!providerState.isProcessAvailable) {
    throw new expression_error_1.ExpressionError('not accessible via UI, please run node', {
        runIndex,
        itemIndex,
    });
}
if (providerState.isEnvAccessBlocked) {
    throw new expression_error_1.ExpressionError('access to env vars denied', {
        causeDetailed: 'If you need access please contact the administrator to remove the environment variable ‘N8N_BLOCK_ENV_ACCESS_IN_NODE‘',
        runIndex,
        itemIndex,
    });
}
return providerState.env[name.toString()];

読み取れることは3つです。

  • N8N_BLOCK_ENV_ACCESS_IN_NODE が文字列の 'false' でなければ、拒否です。変数が無いときも拒否になります。
  • 拒否のときは、$env から名前を読んだ時点で「access to env vars denied」を投げます。
  • 許可のときは、n8nのプロセスの環境変数(process.env)から値を返します。

比べ方が !== 'false' なので、False や FALSE、0 はどれも拒否として扱われます。

既定で拒否になったのは2.0から

公式の「v2.0 Breaking changes」には、セキュリティのため、Codeノードから環境変数へのアクセスを既定で止めると書かれています。N8N_BLOCK_ENV_ACCESS_IN_NODE の既定値は true になりました。移行の手順には、Codeノードで環境変数が要るなら N8N_BLOCK_ENV_ACCESS_IN_NODE=false を設定する、とあります。上のソースも、この説明と同じ動きです。

一方で、環境変数の一覧にある「Security」のページの表は、既定値を false と書いたままです。この表だけを見て「何も設定しなければ読める」と考えると、ここでつまずきます。

エラーの説明どおりに変数を消しても、読めない

このエラーの説明(causeDetailed)には、「管理者に頼んで環境変数 N8N_BLOCK_ENV_ACCESS_IN_NODE を取り除いてもらってください」とあります。しかし上のソースでは、変数が無い状態は拒否です。変数を消すのではなく、false を入れます。

判定するのは、ワークフローを実行しているn8nのプロセス

2.19.2のCodeノードは、JavaScriptのコードをtask runner(Codeノードのコードを実行する専用の仕組み)で動かします。ジョブを始めるときに、ノードを実行しているn8nのプロセスが createEnvProviderState() を呼び、拒否するかどうかを決めます。決めた結果(isEnvAccessBlocked)は、task runnerへそのまま渡ります。$env を使うコードなら、環境変数の値もn8nのプロセスから渡ります。そのため、task runnerの側にだけ false を入れても効きません。

キューモード(実行をworkerという別のプロセスに任せる構成)では、実行するプロセスが1つではありません。スケジュールやWebhookから始まる実行は、キューに入り、workerが受け持ちます。エディタから手動で実行したものは、OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS が true でなければ、mainのプロセスで動きます。

const shouldEnqueue = process.env.OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS === 'true'
    ? this.executionsConfig.mode === 'queue'
    : this.executionsConfig.mode === 'queue' && data.executionMode !== 'manual';

キューモードでは、mainとworkerの両方に同じ値を渡します。

直し方

1. $envを使っているワークフローを洗い出す

先に、影響を受けるワークフローを数えます。n8nのコマンドで、ワークフローを1本ずつJSONファイルに書き出します。ファイル名はワークフローのIDになります。

n8n export:workflow --all --separate --output=wf/
grep -lE '\$env([^A-Za-z0-9_]|$)' wf/*.json
grep -ohE '\$env(\.[A-Za-z0-9_]+|\[[^]]*\])' wf/*.json | sort | uniq -c

2行目は、$env を含むファイルの一覧です。3行目は、読んでいる変数の名前と回数です。$env['X'] のような角括弧の読み方も拾います。const { X } = $env のような書き方は3行目に出ません。漏れの確認は、2行目の一覧で行います。どちらも名前だけで、値は出ません。Dockerで動かしているなら、コンテナの中で実行します。

2. 許可してよいかを決める

false にすると、式やCodeノードから、n8nのプロセスが持つ環境変数を名前で読めます。読めるのは、n8nのプロセスが持つ環境変数のすべてです。APIキーのほか、暗号化の鍵(N8N_ENCRYPTION_KEY)やデータベースのパスワード(DB_POSTGRESDB_PASSWORD など)を環境変数で渡している構成なら、ワークフローを編集できる人は、それらも読めます。「v2.0 Breaking changes」も、機密のデータには環境変数ではなく、Credentialsなどを使うよう勧めています。

編集する人が限られていて、キーの差し替えを1か所で済ませたいなら、許可する方法が合います。編集する人が多いなら、キーをCredentials(認証情報を暗号化して保存する機能)に置き、$env を使わない形に寄せます。

3. n8nを起動する環境に値を渡す

シェルから起動するなら、n8n start の前に環境変数を入れます。弊社では、起動スクリプトが環境変数のファイルを読み込み、そのあとで n8n start を実行しています。

# 環境変数のファイル(名前と置き場所は任意)
export N8N_BLOCK_ENV_ACCESS_IN_NODE=false
export OPENAI_API_KEY="XXXX"

# 起動スクリプトの中
source /path/to/env-file
n8n start

Docker Composeなら、n8nのサービスの environment に1行足します。キューモードでworkerのサービスがあるなら、そちらにも同じ1行を足します。docker run なら -e N8N_BLOCK_ENV_ACCESS_IN_NODE=false を付けます。

services:
  n8n:
    environment:
      - N8N_BLOCK_ENV_ACCESS_IN_NODE=false
  # キューモードの worker(サービス名は構成に合わせる。ほかの設定は省略)
  n8n-worker:
    environment:
      - N8N_BLOCK_ENV_ACCESS_IN_NODE=false

値は小文字の false だけを書きます。弊社は同じ場所でタイムゾーンも設定しています。設定しないと時刻がずれる件は、n8nのスケジュールが13時間ずれる原因と直し方|タイムゾーン設定にまとめました。

4. n8nを再起動する

環境変数は、プロセスを起動するときに渡されます。値を足したら、n8nを止めて起動し直します。

Docker Composeの docker compose restart では、環境変数の変更は反映されません。Dockerの公式ドキュメントにそう書かれています。docker compose up -d を使うと、設定が変わったコンテナを止めて作り直します。

直ったかを確かめる

動いているn8nのプロセスに値が入っているか

ファイルに書いたかではなく、いま動いているプロセスが値を持っているかを見ます。まず、n8nのプロセスの番号(PID)を探します。

ps ax -o pid,command | grep '[n]8n'

2.19.2は、n8n start でも、引数なしの n8n でもサーバーが起動します。この探し方なら、どちらで起動していても拾えます。

workerや、Codeノードのコードを動かすtask runner(起動コマンドに @n8n/task-runner を含むプロセス)など、n8nという文字を含むほかのプロセスも出ます。task runnerには、n8nが決まった環境変数だけを渡します。task runnerのプロセスで N8N_BLOCK_ENV_ACCESS_IN_NODE が出なくても、n8nの本体に入っていないとは限りません。表示された起動コマンドを見て、n8n start(または引数なしの n8n)のプロセスを選びます。

弊社がscreen(端末を閉じてもプログラムを動かし続ける道具)でn8nを起動していた頃は、見つかった番号の先頭を使うと、外側のscreenのプロセスを拾っていました。nodeで動いているn8nの実体を選びます。

見つけた番号で、そのプロセスの環境変数から、この変数だけを取り出します。n8nと同じユーザーで実行します。n8nをrootや専用のユーザーで動かしているなら、sudo を付けます。

ps eww <PID> | tr ' ' '\n' | grep '^N8N_BLOCK_ENV_ACCESS_IN_NODE='

N8N_BLOCK_ENV_ACCESS_IN_NODE=false と出れば、入っています。ほかの環境変数は表示されないので、APIキーが画面に出ることもありません。

1行も出ないときは、実行したユーザーを確かめます。別のユーザーが動かしているプロセスだと、値が入っていても何も表示されないことがあります。弊社のMacで、n8nと同じ一般ユーザーのまま ps eww で見ると、n8nのプロセスでは環境変数が表示され、rootで動いている別の常駐プログラムでは1つも表示されませんでした。n8nと同じユーザーか sudo で実行して、それでも1行も出なければ、そのプロセスには渡っていません。

キューモードでは、mainとworkerのプロセスを1つずつ確かめます。Docker Composeなら、次のように確かめます。

docker compose exec n8n env | grep '^N8N_BLOCK_ENV_ACCESS_IN_NODE='

workerは、n8n の部分をworkerのサービス名に替えて実行します。

Codeノードで、この変数の値を返してみる

テスト用のワークフローにCodeノードを1つ置き、次の1行だけを書いて実行します。

return [{ json: { flag: $env.N8N_BLOCK_ENV_ACCESS_IN_NODE } }];

出力に "flag": "false" と出れば、Codeノードから環境変数を読めています。まだ拒否されていれば、同じ「access to env vars denied」が出ます。読む相手をこの変数にしているので、キーの値を画面に出さずに確かめられます。

キューモードでは、エディタからの手動の実行は、既定でmainのプロセスで動きます。この方法で確かめられるのは、mainの分だけです。workerの分は、上のプロセスの確認か、スケジュールで動いた実行の結果で確かめます。

再起動のあとに、このエラーで止まった実行があるか

SQLiteで動かしているなら、データベースに残った実行から数えられます。実行データの保存先(N8N_EXECUTION_DATA_STORAGE_MODE)が既定の database で、失敗した実行を残す設定(EXECUTIONS_DATA_SAVE_ON_ERROR)が既定の allのときの方法です。再起動の前に、いちばん新しい実行の番号を控えます。実行の番号は、新しい実行ほど大きくなります。

sqlite3 ~/.n8n/database.sqlite "SELECT MAX(id) FROM execution_entity;"

再起動してしばらく動かしたら、控えた番号より後の実行だけを数えます。<控えた番号> は、さきほど出た数字に置き換えます。

sqlite3 ~/.n8n/database.sqlite "SELECT COUNT(*) FROM execution_entity e JOIN execution_data d ON d.executionId = e.id WHERE e.id > <控えた番号> AND d.data LIKE '%access to env vars denied%';"

0 なら、再起動のあとにこのエラーで止まった実行はありません。このエラーを含む実行の数を、再起動の前と後で比べるだけでは足りません。n8nは既定で古い実行の記録を消していくので、消えた分と増えた分が相殺して、新しい拒否を見落とすことがあります。

それでも直らないとき

値が false と完全に一致していない

比べ方は !== 'false' です。False や 0 では拒否のままです。上の ps eww の表示で、実際に入っている文字を確かめます。

設定したプロセスと、動いているプロセスが違う

ターミナルで export しただけでは、別の仕組みで起動したn8nには渡りません。起動の方法が2つ混ざると、値を持たないほうのn8nが動いていることがあります。

弊社で起きたのは、ポートの取り合いでした。常駐させる仕組み(LaunchDaemon)とは別に、screenでもn8nを立ててしまいました。screenのn8nがポート5678を使っていたため、LaunchDaemonのほうは15秒おきに起動に失敗し続けました。ログには port 5678 is already in use が出続けます。いまは起動の方法をLaunchDaemonの1本にしています。

拒否は消えたのに、値が空になる

許可されていても、その名前の環境変数がプロセスに無ければ、undefined が返ります。エラーにはならず、キーが空のまま次の処理へ進みます。

弊社では、名前のよく似た環境変数のファイルが2つありました。2026年7月12日にキーの置き場所を整理したとき、片方が取り残されていました。放置していたら、再起動したときにトークンの変数が欠け、動いているワークフロー67本が止まる状態でした。2026年7月13日に1つにまとめています。

式の欄の表示で判断しない

エディタ(ブラウザで開く編集画面)のコードにも、同じ形の判定が入っています。ただしブラウザ側のコードは、環境変数を空の {} として持ちます。N8N_BLOCK_ENV_ACCESS_IN_NODE も、空の {} から読みます。

isEnvAccessBlocked:e?{}.N8N_BLOCK_ENV_ACCESS_IN_NODE!==`false`:!1,env:{}

ブラウザ側のこの判定は、サーバーの環境変数を見ていません。サーバー側を直したかどうかは、ノードを実行した結果で判断します。

移行の点検で何も出なかった

n8n本体には、2.0で影響を受けるワークフローを探す規則が入っています。式とCodeノードでの process.env のアクセスを扱う規則(process-env-access-v2)の中身は、次のとおりです。

if (process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE) {
    return {
        isAffected: false,
        issues: [],
    };
}
const processEnvPattern = /process\s*(?:\/\*[\s\S]*?\*\/)?\s*\??\.?\s*env\b/;

探しているのは、この正規表現に合う process.env の文字列です。$env は探していません。そのため、$env だけを使うワークフローは、変数があってもなくても「影響なし」になります。点検で何も出なくても、直し方の1で作った一覧で確かめます。

起動の環境変数を自分で変えられない

n8nの起動の設定に手が届かない環境では、N8N_BLOCK_ENV_ACCESS_IN_NODE を渡せません。その場合は、$env を使わない形に書き換えます。

  • APIキーやトークンは、Credentialsに登録します。HTTP Requestノードなら、認証の選択肢に「Predefined Credential Type」と「Generic Credential Type」があり、登録したCredentialsを選べます。
  • キーではない設定値は、Variablesに置けます。式やCodeノードからは $vars.XXXX で読みます。公式ドキュメントによると、Variablesは読み取り専用です。使えるのは、n8n CloudのProとEnterprise、自分で動かす場合のBusinessとEnterpriseです。作れるのは、インスタンスのオーナーと管理者だけです。

まとめ

  • 「access to env vars denied」は、$env の読み取りが拒否の設定のときに出ます。
  • 2.0から既定は拒否です。N8N_BLOCK_ENV_ACCESS_IN_NODE が文字列の false のときだけ許可されます。
  • 値は、ワークフローを実行するn8nのプロセスに渡します。キューモードなら、mainとworkerの両方です。Docker Composeは up -d で作り直します。
  • 確かめるときは、動いているプロセスの環境変数と、ノードを実行した結果を見ます。式の欄の表示と移行の点検では判断しません。
  • 許可すると、編集できる人は、n8nのプロセスの環境変数をすべて読めます。避けたいときや設定を変えられないときは、CredentialsやVariablesへ移します。

参考にした公式ドキュメント

n8nの点検と構築のご相談

「Codeノードで環境変数が読めない」「ワークフローが知らないうちに止まっている」といったn8nの不具合の点検や、業務の自動化の仕組みづくりをお引き受けしています。

お問い合わせはこちら

この記事を書いた人

貫名 孝夫(マルタマーケティング株式会社 代表取締役)。n8nで140本を超えるワークフローを本番で動かしながら、実際に起きた不具合と直し方を記録しています。会社概要