Start your project

VL Studio / Blog / n8n

n8n 2.0 broke my workflows: every breaking change and the safe upgrade path

Most n8n 2.0 breakage comes from new secure defaults, not bugs: Code nodes can no longer read environment variables, Execute Command is disabled, file nodes are fenced into one folder, and code runs in task runners. Each one has a one-line fix, and the built-in Migration Report finds them before you upgrade.

By VL StudioUpdated October 17, 202611 min read

Luminous glass pillars of different heights — an illustration of version upgrades

Key takeaways

  • n8n 2.0.0 shipped on December 5, 2025 as a "hardening release" (n8n release notes, 2025): safer defaults and removed legacy options, not new features.
  • The four changes behind most broken workflows: N8N_BLOCK_ENV_ACCESS_IN_NODE now defaults to true, Execute Command and Local File Trigger are disabled, file nodes only reach ~/.n8n-files, and edits need Publish to go live.
  • Python Code nodes now need task runners in external mode, which means a separate n8nio/runners container on the exact same version as n8n.
  • MySQL/MariaDB as n8n's own database is gone. Move to PostgreSQL or SQLite before upgrading.
  • Run Settings > Migration Report on your 1.x instance, back up the .n8n folder and the database, pin versions, and keep the old image tag for rollback.

The short answer

If your workflows stopped working right after moving to n8n 2.x, the cause is almost always a changed default, not data loss. n8n's own release notes call 2.0 a hardening release built around "secure-by-default execution" and the "removal of legacy options." That means settings that used to be permissive are now locked, and a few old options no longer exist.

The fastest diagnosis: open the failing execution, find the node that errored, and match it against the table below. If you have not upgraded yet, stop here and run the Migration Report first. It does the matching for you.

Every n8n 2.0 breaking change: symptom and fix

This table covers every change on n8n's official v2.0 breaking changes page, in the order you are most likely to hit them. Environment variables go in your Docker Compose environment: block, your .env file, or your shell for npm installs.

ChangeWhat you seeFix
Env access blocked in Code node (N8N_BLOCK_ENV_ACCESS_IN_NODE default false → true)$env.MY_KEY fails or returns nothing in Code nodes and expressions that used to workMove secrets into credentials (preferred), or set N8N_BLOCK_ENV_ACCESS_IN_NODE=false
Execute Command and Local File Trigger disabledNodes show as unknown or missing; workflows using them failRe-enable via NODES_EXCLUDE, for example NODES_EXCLUDE="[]" to enable all nodes
File access restricted (N8N_RESTRICT_FILE_ACCESS_TO now defaults to ~/.n8n-files)Read/Write Files from Disk or Read Binary Files errors on paths that worked beforeMove files into ~/.n8n-files, or set N8N_RESTRICT_FILE_ACCESS_TO to your directory
Save and Publish split (Activate/Deactivate replaced by Publish/Unpublish)You edited and saved, but production still runs the old versionClick Publish. On the CLI, update:workflow is replaced by publish:workflow / unpublish:workflow
Task runners on by default (N8N_RUNNERS_ENABLED → true)Code nodes behave differently, run slower on first call, or require() of a package failsTest on 1.x with N8N_RUNNERS_ENABLED=true first; in external mode, add packages to the runners image and allowlist them
$evaluateExpression() no longer works in Code nodeReturns null or an errorRewrite in plain JavaScript, or evaluate in an Edit Fields (Set) node first. Last resort: N8N_RUNNERS_INSECURE_MODE=true
Pyodide Python replaced by native PythonPython Code nodes fail; _input and dot access stop workingRun task runners in external mode and rewrite affected Python
Task runner removed from n8nio/n8n imageExternal-mode runner container built from n8nio/n8n no longer worksUse the n8nio/runners image, same version tag as n8n
OAuth callback needs auth (N8N_SKIP_AUTH_ON_OAUTH_CALLBACK → false)OAuth credential connect fails at the callback stepConnect while logged in to n8n; test with N8N_SKIP_AUTH_ON_OAUTH_CALLBACK=false on 1.x before upgrading
Settings file permissions enforced (N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS → true)Startup warning or error about the config filechmod 600 the config file in .n8n; set false only on Windows
Git node blocks bare repos (N8N_GIT_NODE_DISABLE_BARE_REPOS → true)Git node fails on bare repositoriesN8N_GIT_NODE_DISABLE_BARE_REPOS=false
Sub-workflow return data changedA parent workflow now gets the child's output (not its input) when the child hits a Wait nodeUpdate parent logic to read the child's real output
Start node removedOld workflows built on the Start node won't runReplace with Manual Trigger, or Execute Workflow Trigger for sub-workflows
MySQL/MariaDB database support droppedn8n cannot start against a MySQL/MariaDB backendMigrate to PostgreSQL or SQLite before upgrading (MySQL node still works)
SQLite legacy driver removedMostly invisible; the pooled driver (WAL mode) becomes the only oneTest early with DB_SQLITE_POOL_SIZE=2 on 1.x
In-memory binary data mode removedDisk or database grows with files; N8N_AVAILABLE_BINARY_DATA_MODES ignoredSet N8N_DEFAULT_BINARY_DATA_MODE to filesystem, database or s3; check free disk
QUEUE_WORKER_MAX_STALLED_COUNT removedStalled queue-mode jobs are no longer retried automaticallyDelete the variable; add your own retry or error workflow
N8N_CONFIG_FILES removedSettings from your JSON config file silently stop applyingMove them to env vars, a .env file, or _FILE variables
dotenv library upgradedA .env value is parsed differently (backticks, multiline, # lines)Quote values that contain backticks; review the file line by line
n8n --tunnel removedThe flag is not recognizedUse ngrok, localtunnel or Cloudflare Tunnel
Release tags renamed: latest/next → stable/betaAuto-updating images jump versions unexpectedlyPin an exact version, such as n8nio/n8n:2.0.0
Hooks workflow.activeChange/activeChangeCurrent deprecatedCustom external hooks stop firingUse workflow.published
Nodes removed: Spontit, crowd.dev, Kitemaker, Automizy"Unrecognized node type" errorsReplace with HTTP Request or remove

The four changes that break the most workflows

1. Code nodes can't read environment variables

This is the classic "n8n code node env variables blocked" search. N8N_BLOCK_ENV_ACCESS_IN_NODE is the setting that controls whether Code nodes and expressions can read server environment variables. Many teams stored API keys in the server environment and read them with $env. In 2.0 that is blocked by default, because anyone who can edit a workflow could otherwise read every secret on the server. The right fix is to put the key in an n8n credential (for example a Header Auth credential on an HTTP Request node). If you must keep the old behavior while you refactor:

environment:   # docker-compose.yml, n8n service
  - N8N_BLOCK_ENV_ACCESS_IN_NODE=false

2. Execute Command and file nodes stop working

Execute Command and Local File Trigger are excluded by default because they "allow users to run arbitrary commands and access the file system." File reads and writes are fenced to ~/.n8n-files. In Docker, that path is inside the container (under /home/node), so a bind mount pointing somewhere else will now fail. Re-enable only what you need:

environment:
  - NODES_EXCLUDE=[]                          # re-enables all nodes, including Execute Command
  - N8N_RESTRICT_FILE_ACCESS_TO=/data/shared  # directory your file nodes may use

Think twice before re-enabling. These defaults changed for security. If several people can edit workflows on your instance, an enabled Execute Command node is effectively shell access to the server.

3. "I saved it, but production didn't change"

In 2.0, Save keeps your edits without touching production, and Publish pushes them live (see n8n's save and publish docs). If a webhook keeps returning old behavior after an edit, you probably saved without publishing. For other webhook problems, see our guide to the n8n production webhook not working.

4. Task runners and the separate container

Task runners are separate processes that execute Code node JavaScript and Python away from the main n8n process. In 2.0, all Code node executions run in them. In the default internal mode n8n starts the runner as a child process, so JavaScript keeps working without new containers. External mode puts the runner in a separate sidecar container and is what n8n recommends for production. It is also required for the Python Code node. Two details trip people up: the n8nio/runners tag must match the n8nio/n8n tag exactly, and in queue mode every worker needs its own runner sidecar. A minimal Compose setup:

services:
  n8n:
    image: n8nio/n8n:${N8N_VERSION}
    container_name: n8n-main
    environment:
      - N8N_RUNNERS_MODE=external
      - N8N_RUNNERS_BROKER_LISTEN_ADDRESS=0.0.0.0
      - N8N_RUNNERS_AUTH_TOKEN=${RUNNERS_TOKEN}
    ports:
      - "5678:5678"
    volumes:
      - n8n_data:/home/node/.n8n

  task-runners:
    image: n8nio/runners:${N8N_VERSION}   # must equal the n8n tag
    environment:
      - N8N_RUNNERS_TASK_BROKER_URI=http://n8n-main:5679
      - N8N_RUNNERS_AUTH_TOKEN=${RUNNERS_TOKEN}
    depends_on:
      - n8n

volumes:
  n8n_data:

If a Code node used require('moment') or a Python library like pandas, the runner image does not have it. n8n's task runner docs show how to extend n8nio/runners with your packages and allowlist them through NODE_FUNCTION_ALLOW_EXTERNAL or N8N_RUNNERS_EXTERNAL_ALLOW in the launcher config.

Pre-upgrade checklist

  1. Update to the latest 1.x first. n8n's update guide warns against jumping many versions at once. The Migration Report also needs a recent 1.x: n8n's announcement says it is available since 1.121.0 (another line in the same post says 1.119.0).
  2. Run the Migration Report. The Migration Report is a built-in compatibility scan. Go to Settings > Migration Report (global admins only). It shows "X out of Y workflows are compatible with n8n 2.0" and splits issues into a Workflow issues tab and an Instance issues tab, tagged Critical, Medium or Low. Fix the Critical items, click Refresh, and repeat until both tabs are clean (migration tool docs).
  3. Pre-flip the new defaults on 1.x. Set N8N_RUNNERS_ENABLED=true, N8N_BLOCK_ENV_ACCESS_IN_NODE=true, N8N_SKIP_AUTH_ON_OAUTH_CALLBACK=false and DB_SQLITE_POOL_SIZE=2 (for SQLite). Test your key workflows. Whatever breaks now would have broken during the upgrade.
  4. Take a complete backup. Back up the .n8n folder, which holds the encryption key and the SQLite database, plus your PostgreSQL dump if you use Postgres. A database backup without the encryption key can't decrypt your credentials.
  5. Export workflows and credentials separately, as a second, portable copy.
  6. Write down your current version tag and your full environment. That is your rollback target.
  7. Run a staging copy with the backup restored, upgrade that first, and run your important workflows end to end.
  8. Move off MySQL/MariaDB as the n8n backend if you are on it. n8n says to move data to PostgreSQL or SQLite before upgrading. The export:entities/import:entities commands added in 1.116 are documented for SQLite and Postgres, so check the current CLI docs before relying on them for a MySQL source.
mkdir -p ~/n8n-backup    # Docker, container "n8n", default SQLite
docker exec n8n n8n export:workflow --backup --output=/home/node/.n8n/backups/workflows/
docker exec n8n n8n export:credentials --backup --output=/home/node/.n8n/backups/credentials/
docker compose stop n8n          # stop before copying a SQLite database
docker run --rm -v n8n_data:/data -v ~/n8n-backup:/backup alpine \
  tar czf /backup/n8n_data-$(date +%F).tgz -C /data .
pg_dump -Fc -h localhost -U n8n n8n > ~/n8n-backup/n8n-$(date +%F).dump   # Postgres only

Use separate output folders for workflows and credentials. n8n's backup docs note a known issue where importing credentials fails if workflow files share the folder. The --backup exports do not include users, execution history, variables or the encryption key, so they never replace the full volume backup.

The safe upgrade procedure

Docker Compose

N8N_VERSION=2.0.0        # in .env: pin the exact target, never "latest"

docker compose pull
docker compose down
docker compose up -d
docker compose logs -f n8n   # watch the database migrations finish

Wait for startup to finish. The first boot runs database migrations, and interrupting it is the riskiest moment of the whole upgrade. This follows n8n's Docker update steps, with a pinned tag added. Then open the UI, check that workflows and credentials are present, and run one manual test of each critical workflow. Confirm production workflows are published.

npm

npm install -g n8n@2.0.0   # stop n8n first (systemd, pm2, etc.)
n8n --version
n8n start

For npm installs, the new defaults apply to the shell or service environment that starts n8n. Put your overrides in the systemd unit or .env file, not only in an interactive shell.

Rollback steps

n8n's backup and restore guide lists rolling back a failed update as a core reason to keep full backups. The upgrade migrates your database schema, so simply starting the old image against the upgraded database is not a safe rollback. The clean path is to restore what you backed up:

  1. Stop n8n (and runner containers).
  2. Restore the .n8n folder or the n8n_data volume from your archive.
  3. Restore the PostgreSQL dump if you use Postgres.
  4. Restore your old environment variables, including N8N_ENCRYPTION_KEY if you set one.
  5. Set N8N_VERSION back to the 1.x tag you wrote down and start.
docker compose down
docker run --rm -v n8n_data:/data -v ~/n8n-backup:/backup alpine \
  sh -c "rm -rf /data/* && tar xzf /backup/n8n_data-YYYY-MM-DD.tgz -C /data"
pg_restore --clean -h localhost -U n8n -d n8n ~/n8n-backup/n8n-YYYY-MM-DD.dump   # Postgres only
sed -i 's/^N8N_VERSION=.*/N8N_VERSION=<old 1.x tag>/' .env
docker compose up -d

n8n's npm docs also describe n8n db:revert, run on the current version, which rolls back one database migration per run. With a jump as large as 1.x to 2.0, restoring the backup is simpler and more predictable. Anything that ran after the upgrade, such as new executions or edits, is lost with a restore, so roll back quickly or re-apply those changes by hand. One more reason not to wait: n8n said 1.x would get security and bug fixes for only three months after the 2.0 release.

FAQ

Why does $env return an error in my Code node after upgrading to n8n 2.0?

n8n 2.0 changed the default of N8N_BLOCK_ENV_ACCESS_IN_NODE from false to true, so Code nodes can no longer read environment variables. Move secrets into credentials, or set N8N_BLOCK_ENV_ACCESS_IN_NODE=false if you accept the risk.

Do I need a separate container for n8n task runners?

Not for JavaScript: task runners are on by default in n8n 2.0 and internal mode runs them as a child process. You need the separate n8nio/runners container for external mode, which is required for the Python Code node and recommended by n8n for production. Its version must match your n8nio/n8n image.

Can I downgrade from n8n 2.0 back to 1.x?

Yes, but the database has been migrated, so the clean path is to stop n8n, restore the .n8n folder and database backup you took before the upgrade, and start the old pinned image. n8n also documents n8n db:revert, which rolls back one database migration per run.

Where is the n8n 2.0 Migration Report?

In a recent 1.x instance under Settings > Migration Report. Only global admins can see it. It lists workflow issues and instance issues by severity, and a clean report means you are ready to upgrade.

Credential trouble right after an upgrade is often OAuth-related. Our guide to Google OAuth tokens expiring every 7 days in n8n covers the most common case.

Rather not do this on a live instance? We'll upgrade it for you.

Our n8n / Make Workflow Repair starts with a diagnosis within 24 hours on business days ($149, credited toward the repair), then a fixed-price repair or upgrade from $390, with 20% off for new customers. If the workflows need a rebuild, Workflow Automation starts at $2,500.

Sources

  1. n8n Docs: v2.0 breaking changes
  2. n8n Docs: v2.0 Migration tool (Migration Report)
  3. n8n Docs: Release notes 2.x (n8n@2.0.0, December 5, 2025)
  4. n8n Blog: Introducing n8n 2.0
  5. n8n Docs: Set up task runners
  6. n8n Docs: Back up and restore
  7. n8n Docs: CLI commands (export, import, entities)
  8. n8n Docs: Install with Docker (updating)
  9. n8n Docs: Install with npm (updating and reverting)
  10. n8n Docs: Save and publish workflows

VLWritten by the VL Studio team — we build and repair automations, AI receptionists and websites for small businesses in the US and Europe.

↑