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

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 Docs|v2.0 Breaking changes
- n8n Docs|Security(環境変数)
- n8n Docs|n8n metadata
- n8n Docs|Define custom variables
- n8n Docs|Enable queue mode
- Docker Docs|docker compose restart
- Docker Docs|docker compose up
n8nの点検と構築のご相談
「Codeノードで環境変数が読めない」「ワークフローが知らないうちに止まっている」といったn8nの不具合の点検や、業務の自動化の仕組みづくりをお引き受けしています。






