n8n 2.0 went stable on 15 December 2025. The current stable line is 2.36.x. n8n supported 1.x for three months after the 2.0 release — security and bug fixes only, no new features — which puts the end of support at roughly 15 March 2026 (n8n blog, release notes 2.x). A 1.x instance today is unpatched software.
If you are still on 1.x, the upgrade is not a version bump. n8n described 2.0 as a security, reliability and performance release — “secure by default” — which in practice means several things that worked in 1.x are now off, and you have to turn them back on deliberately.
This is the list of what breaks on a self-hosted Docker instance, in the order you will hit them, with the exact environment variable for each.
Verified 28 August 2026. Every claim below links to the source. Where I am reasoning rather than quoting, I say so.
Before you upgrade: run the Migration Report
n8n ships a Migration Report in Settings → Migration Report, available to global admins on 1.119.0 and higher. It scans your existing workflows and tags each issue by severity; critical issues are the ones that will break execution (n8n blog).
Run it on 1.x, before you pull the 2.x image. After the upgrade you are debugging blind. This is the single step most upgrade posts skip, and it is the only one that tells you about your workflows rather than the general case.
Back up first, too: your .n8n volume holds the encryption key. Without it, every stored credential is unrecoverable — n8n’s own docs note the directory holds encryption keys and instance logs and should be persisted even when you use PostgreSQL (Install with Docker).
docker compose down
docker run --rm -v n8n_data:/data -v "$PWD":/backup alpine
tar czf /backup/n8n_data_$(date +%F).tar.gz -C /data .
# using Postgres? dump it too
docker compose exec -T postgres pg_dump -U n8n n8n | gzip > n8n_db_$(date +%F).sql.gz
1. The Execute Command node is gone from the panel
This is the first thing most self-hosters notice, because the node simply is not in the node picker any more.
In 2.0, nodes that allow arbitrary command execution are disabled by default (n8n blog). The mechanism is NODES_EXCLUDE, whose default value is now:
["n8n-nodes-base.executeCommand", "n8n-nodes-base.localFileTrigger"]
Source: node environment variables.
The trap is the variable name. The documented variable is NODES_INCLUDE — no N8N_ prefix. A user filed a bug after setting N8N_NODES_INCLUDE=n8n-nodes-base.executeCommand and finding the node still missing; the issue was closed as working-as-expected (issue #23439). N8N_NODES_INCLUDE is not a real variable, so it was silently ignored — which is exactly how this wastes an afternoon, because nothing in the log tells you the name is wrong.
The fix is documented — just not where you would look for it. The NODES_EXCLUDE entry itself says: “To enable all nodes, specify NODES_EXCLUDE: "[]"“ (nodes env vars). You clear the exclusion list rather than adding to an include list:
environment:
- NODES_EXCLUDE=[]
In a .env file the same value is written NODES_EXCLUDE="[]". A community thread arrives at the same answer if you want to see someone else’s working config (thread).
Two notes worth taking seriously. First, [] re-enables both excluded nodes, including Local File Trigger — if you only want one back, list the other explicitly instead of emptying the array. Second, these nodes are excluded by default for a reason: anyone who can edit a workflow on your instance can run shell commands as the n8n user. If your instance has more than one editor, leave it excluded and move the shell work into a container you control.
2. WEBHOOK_URL is deprecated — webhooks show the wrong host
If your production webhook URLs come back as http://localhost:5678/webhook/... or with an internal port glued on, this is the cause.
WEBHOOK_URL is deprecated as of n8n 2.35.0, replaced by N8N_WEBHOOK_URL. Behind a reverse proxy you also need to tell n8n how many proxy hops to trust (configure webhook URLs with a reverse proxy):
environment:
- N8N_WEBHOOK_URL=https://n8n.example.com/
- N8N_PROXY_HOPS=1
The docs are explicit that N8N_HOST, N8N_PROTOCOL and N8N_PORT — the three variables every 1.x-era tutorial tells you to set — are components n8n assembles internally and do not work correctly behind a reverse proxy. Setting the full URL manually is the fix, not a workaround.
N8N_PROXY_HOPS=1 is right for the common case of one proxy in front of n8n (Caddy, nginx, Traefik). If you are behind Cloudflare and a local proxy, that is two hops.
The symptom is worth naming because it is confusing: this bites you at OAuth time, not at webhook time. The redirect URL n8n hands to a provider is built from the same setting, so a wrong value shows up as a failing OAuth callback long before anyone posts to your webhook (community report).
3. Task runners are on by default — and N8N_RUNNERS_ENABLED is dead
In 2.0, Code node executions run in isolated task runners by default (n8n blog). The variable you were told to set in 1.x, N8N_RUNNERS_ENABLED=true, is deprecated from 2.0 and no longer needed (set up task runners).
Confusingly, n8n’s own Docker install page still shows N8N_RUNNERS_ENABLED=true in its example docker run (checked 2026-08-28). It is harmless, but do not take its presence as a sign you need it.
What you choose now is the mode:
N8N_RUNNERS_MODE=internal— the runner launches as a child process of n8n. Simplest. The docs do not recommend it for production.N8N_RUNNERS_MODE=external— a separaten8nio/runnerscontainer. Recommended for production.
External mode, per the docs:
services:
n8n:
image: n8nio/n8n
environment:
- N8N_RUNNERS_MODE=external
- N8N_RUNNERS_BROKER_LISTEN_ADDRESS=0.0.0.0
- N8N_RUNNERS_AUTH_TOKEN=${RUNNER_TOKEN}
task-runners:
image: n8nio/runners
environment:
- N8N_RUNNERS_TASK_BROKER_URI=http://n8n:5679
- N8N_RUNNERS_AUTH_TOKEN=${RUNNER_TOKEN}
- N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT=15
Pin both images to the same tag. The broker protocol is not something to run mismatched versions across, and n8n’s own example pins both sides.
The part that costs money: external mode is a second long-running Node process. On a 1 GB VPS that was comfortable under 1.x, you are now fitting n8n, a runner and (if you use it) Postgres into the same memory. n8n does not publish a hard minimum spec — I could not find one, and any guide quoting an official figure is quoting something that is not there. But the direction is not in doubt: 2.x with external runners needs more RAM than 1.x did, and 1 GB is where people start seeing the container OOM-killed mid-execution. Budget 2 GB.
4. Environment variables are blocked inside the Code node
2.0 blocks Code nodes from reading the host’s environment variables (n8n blog). If a workflow did process.env.SOME_API_KEY, it now gets nothing.
This one is not a config bug to route around — it is the point of the release. The right fix is to move those values into n8n credentials, which is where they should have been: credentials are encrypted at rest with your instance key, and they do not leak into execution logs the way an inlined env read can.
If you have a genuine reason to import a built-in or external module inside the Code node, those are separate switches and they are still there: NODE_FUNCTION_ALLOW_BUILTIN and NODE_FUNCTION_ALLOW_EXTERNAL, both empty by default (nodes env vars).
5. EACCES: permission denied, open '/home/node/.n8n/config'
Not new in 2.0 — this one is older than most n8n installs — but the upgrade is when people recreate their volumes and meet it.
The cause is a UID mismatch. n8n runs as the node user inside the container (UID 1000). If you bind-mount a host directory (-v ~/.n8n:/home/node/.n8n) owned by a different UID, the container cannot write its config file (issue #1240, still recurring in #11102 on 1.61.0).
Two fixes, in order of preference:
# Best: use a named Docker volume and never think about UIDs again
docker volume create n8n_data
# ... -v n8n_data:/home/node/.n8n
# If you insist on a bind mount, hand it to UID 1000
sudo chown -R 1000:1000 ~/.n8n
Related, and worth setting deliberately: N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true appears in n8n’s documented Docker command. It makes n8n enforce restrictive permissions on its own settings file. Keep it on; if it fires an error, that is the setting telling you your config file is world-readable, which on a shared box it should not be.
The upgrade, start to finish
# 1. On 1.x: Settings → Migration Report. Fix every critical item first.
# 2. Back up the volume AND the database (commands at the top of this article).
# 3. Edit compose: pin a 2.x tag, do not use :latest for a major upgrade.
# 4. Add the variables you now need:
# NODES_EXCLUDE=[] # only if you truly need those nodes
# N8N_WEBHOOK_URL=https://your.domain/
# N8N_PROXY_HOPS=1
# N8N_RUNNERS_MODE=internal|external
# 5. docker compose pull && docker compose up -d
# 6. docker compose logs -f n8n # read it; deprecation warnings name the variable
# 7. Re-run one workflow of each kind: a webhook, a Code node, a scheduled trigger.
Step 7 is the one to not skip. The three failure modes in this article each surface in a different kind of workflow, and an instance where the editor loads fine can still have every webhook pointing at localhost.
What I could not verify
Being straight about the edges of this:
- No official minimum hardware spec exists. n8n’s docs do not publish one. The 2 GB figure above is my reasoning from what 2.x runs as separate processes, not a vendor requirement. Sizing guides that quote an official minimum are inventing it.
- n8n’s Docker install page still shows a deprecated variable as of 2026-08-28. If that changes, the page is right and this note is stale.
FAQ
Do I have to upgrade to 2.x?
1.x received security and bug fixes for three months after the 2.0 release — a window that closed around 15 March 2026 (n8n blog). Running 1.x today means running software that gets no patches at all, exposed to the internet if your webhooks are.
Can I skip straight from an old 1.x to 2.36?
The Migration Report only exists from 1.119.0. Upgrade to a recent 1.x first so you can run it, then go to 2.x. Two hops, one of which tells you what will break.
Will my credentials survive?
Yes, if the .n8n volume with the encryption key survives. That is the whole reason for the backup step.
Is Cloud affected by any of this?
No. n8n Cloud handles the migration. Everything in this article is a self-hosting cost — which is the honest thing to weigh when you compare the €20/month plan against a $6 VPS.
Some links on this site are affiliate links. If you buy hosting through one, this site earns a commission at no extra cost to you. Prices and specifications in this article were checked on the dates stated and are linked to their sources so you can verify them yourself.
Related: The cheapest VPS that actually runs n8n 2.x — verified prices, checked this month.