Back to blog
LearningsNov 27, 202510 min read

n8n v2.0: Breaking Changes You Need to Know Before December

Learn about the major breaking changes in n8n version 2.0 that will impact your workflows before December.

Updated Apr 8, 2026
n8n v2.0: Breaking Changes You Need to Know Before December

Look, I've been working with n8n deployments for over a year now, and when I saw the v2.0 announcement, my first thought wasn't "what cool features," it was "how many production workflows am I going to have to fix?"

And you know what? This release is genuinely good, but it's going to break your stuff if you don't prepare.

The Timeline (Mark Your Calendar)

  • Beta: December 8, 2025
  • Stable: December 15, 2025
  • Your deadline to test: right now

Version 1.x will get 3 months of security patches after v2 releases, and then that's it.

The Good Stuff

Autosave is coming. Yes, finally. After how many years of people losing work? Better late than never.

Also: updated canvas interface, new sidebar, and "surprises."

But let's be real, you're not here for UI updates.

The Stuff That Will Actually Break Your Workflows

Security: They're Locking Everything Down

1. Code Node Can No Longer Access Environment Variables

bash
N8N_BLOCK_ENV_ACCESS_IN_NODE=true  # This is now the default

If you've been lazy and were accessing process.env.SOME_SECRET in your Code nodes... yeah, that's over.

The fix?:

  • Set it back to false (which defeats the purpose)

I've seen too many people storing API keys in environment variables and accessing them in Code nodes. This forces better practices.

2. Task Runners Are Mandatory

bash
N8N_RUNNERS_ENABLED=true

All Code node executions now run in task runners for isolation. This is good for security, but your infrastructure needs to handle it.

Now they have to be in external mode which is the most secure.

3. Python Code Node: Complete Rewrite

They removed Pyodide (the browser-based Python). Now it's native Python only, task runners required, external mode mandatory.

What breaks:

  • _input variable (removed)
  • Dot notation access (removed)
  • Any Python Code node without proper task runner setup (broken)

This is better long-term, but the migration pain is real. Review every Python Code node you have.

4. ExecuteCommand and LocalFileTrigger: Disabled by Default

Finally. These nodes are security nightmares. They allow users to execute arbitrary system commands and access the file system.

They're disabled now. If you absolutely need them (and you probably don't), you have to explicitly enable them:

bash
NODES_EXCLUDE="[]"  # Or just remove these specific nodes

5. OAuth Callbacks Need Authentication

bash
N8N_SKIP_AUTH_ON_OAUTH_CALLBACK=false  # New default

Before upgrading, test every OAuth integration. Slack, Google, whatever you've got connected—test it all.

6. File Operations Are Sandboxed

bash
N8N_RESTRICT_FILE_ACCESS_TO=~/.n8n-files  # New default

ReadWriteFile and ReadBinaryFiles nodes can only touch files in this directory now. Good for security, annoying if you've been reading files from random locations.

7. Config File Permissions: SSH-Style Security

Your config files need 0600 permissions (owner read/write only).

bash
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true

Windows users: This doesn't work on Windows. Set it to false.

Linux/Mac users: Run chmod 600 on your config files or n8n won't start.

Database Changes (The Painful Ones)

MySQL/MariaDB: Removed

Gone. PostgreSQL or SQLite only. This was deprecated in v1.0—if you're still on MySQL, you've had a year to migrate.

Use the database migration tool or you're screwed.

SQLite: Legacy Driver Removed

Only the pooling driver remains. It's faster (10x in benchmarks), uses WAL mode, and is the new default.

bash
DB_SQLITE_POOL_SIZE=2  # Auto-configured

Most people won't notice this change. If you do, it's probably because something was broken before.

Binary Data: No More In-Memory Mode

The default mode that kept binary data in memory during execution? Gone.

Your options:

  • filesystem (default for single instance)
  • database (default for queue mode)
  • s3 (if you're fancy)

Make sure you have disk space. If you're processing large files and suddenly run out of space, this is why.

Behavior Changes (The Subtle Ones)

Subworkflow + Wait Node = Fixed

Before: Parent workflow received input from Wait nodes in child workflows (which made no sense)

Now: Parent workflow receives output from the end of the child workflow (which is correct)

If you have workflows calling subworkflows with Wait nodes, review them. The behavior change is correct, but your logic might depend on the previous broken behavior.

Configuration Hell

dotenv Update

Your .env file parsing is different now.

Changes:

  • Backticks need quotes now
  • # always starts a comment (no more values containing #)
  • Multi-line values work now

Review your .env files. Especially if you have weird characters in passwords or tokens.

Removed stuff:

  • QUEUE_WORKER_MAX_STALLED_COUNT
  • CLI option n8n --tunnel (use ngrok or Cloudflare Tunnel)
  • update:workflow --all --active=true (good riddance, this was dangerous)

Removed nodes:

  • Spontit
  • crowd.dev
  • Kitemaker

Docker Setup: Old vs New (The Part You Actually Need)

Alright, enough theory. Here's how your docker-compose actually needs to look.

The Old Way (v1.x) - Simple but Insecure

yaml
services:
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: always
    ports:
      - "5678:5678"
    environment:
      - N8N_HOST=your-domain.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://your-domain.com/
      - GENERIC_TIMEZONE=Europe/Berlin
      
      # Database
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=n8n_password
      
      # These were the defaults (and security issues)
      - N8N_BLOCK_ENV_ACCESS_IN_NODE=false  # Code nodes could access env vars
      - N8N_RUNNERS_ENABLED=false  # No task runners
      - N8N_SKIP_AUTH_ON_OAUTH_CALLBACK=true  # No auth on OAuth callbacks
      
    volumes:
      - n8n_data:/home/node/.n8n
      - ./n8n-files:/files  # Files wherever you wanted
    depends_on:
      - postgres

  postgres:
    image: postgres:15
    container_name: n8n_postgres
    restart: always
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=n8n_password
      - POSTGRES_DB=n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  n8n_data:
  postgres_data:

This worked fine but had security holes you could drive a truck through.

The New Way (v2.0) - Option 1: Internal Mode (Simpler)

For most people, this is enough. Task runners run as child processes inside the same container.

yaml
services:
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: always
    ports:
      - "5678:5678"
    environment:
      - N8N_HOST=your-domain.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://your-domain.com/
      - GENERIC_TIMEZONE=Europe/Berlin
      
      # Database
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=n8n_password
      
      # New v2.0 - Internal mode (simple)
      - N8N_RUNNERS_ENABLED=true  # Just this!
      - N8N_BLOCK_ENV_ACCESS_IN_NODE=true
      - N8N_SKIP_AUTH_ON_OAUTH_CALLBACK=false
      
      # File Access Restrictions
      - N8N_RESTRICT_FILE_ACCESS_TO=/home/node/.n8n-files
      
      # File Permissions (Linux/Mac only)
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
      # - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=false  # For Windows
      
      # Binary Data Storage
      - N8N_DEFAULT_BINARY_DATA_MODE=filesystem
      
      # Git Node Security
      - N8N_GIT_NODE_DISABLE_BARE_REPOS=true
      
       # If you want to allow ExecuteCommand and LocalFileTrigger (disabled by default)
      - NODES_EXCLUDE=[]
      
    volumes:
      - n8n_data:/home/node/.n8n
      - n8n_files:/home/node/.n8n-files
    depends_on:
      - postgres

  postgres:
    image: postgres:15
    container_name: n8n_postgres
    restart: always
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=n8n_password
      - POSTGRES_DB=n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  n8n_data:
  n8n_files:
  postgres_data:

When to use internal mode:

  • Simpler setup
  • Don't need Python Code nodes
  • Don't have extreme security requirements
  • Most use cases

The New Way (v2.0) - Option 2: External Mode (Maximum Security)

You only need this if:

  • You use Python Code nodes (external mode is mandatory for Python)
  • You want maximum security isolation
  • You're running in queue mode with workers
yaml
services:
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: always
    ports:
      - "5678:5678"
    environment:
      - N8N_HOST=your-domain.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://your-domain.com/
      - GENERIC_TIMEZONE=Europe/Berlin
      
      # Database
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=n8n_password
      
      # New v2.0 - External Mode
      - N8N_RUNNERS_ENABLED=true
      - N8N_RUNNERS_MODE=external
      - N8N_RUNNERS_BROKER_LISTEN_ADDRESS=0.0.0.0  # Allow external connections
      - N8N_RUNNERS_AUTH_TOKEN=${N8N_RUNNERS_AUTH_TOKEN}
      - N8N_NATIVE_PYTHON_RUNNER=true  # For Python Code nodes
      - N8N_BLOCK_ENV_ACCESS_IN_NODE=true
      - N8N_SKIP_AUTH_ON_OAUTH_CALLBACK=false
      
      # File Access Restrictions
      - N8N_RESTRICT_FILE_ACCESS_TO=/home/node/.n8n-files
      
      # File Permissions (Linux/Mac)
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
      # - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=false  # Windows
      
      # Binary Data Storage
      - N8N_DEFAULT_BINARY_DATA_MODE=filesystem
      
      # Git Node Security
      - N8N_GIT_NODE_DISABLE_BARE_REPOS=true
      
      # If you want to allow ExecuteCommand and LocalFileTrigger (disabled by default)
      - NODES_EXCLUDE="[]"

      
    volumes:
      - n8n_data:/home/node/.n8n
      - n8n_files:/home/node/.n8n-files
    depends_on:
      - postgres

  # External Task Runner (separate container)
  n8n-runner:
    image: n8nio/runners:latest
    container_name: n8n_runner
    restart: always
    environment:
      # Connect to broker in n8n via HTTP/WebSocket on port 5679
      - N8N_RUNNERS_TASK_BROKER_URI=http://n8n:5679
      - N8N_RUNNERS_AUTH_TOKEN=${N8N_RUNNERS_AUTH_TOKEN}  # Same token as n8n
    depends_on:
      - n8n

  postgres:
    image: postgres:15
    container_name: n8n_postgres
    restart: always
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=n8n_password
      - POSTGRES_DB=n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  n8n_data:
  n8n_files:
  postgres_data:

Generate your auth token:

bash
openssl rand -hex 32

Add it to your .env file:

bash
N8N_RUNNERS_AUTH_TOKEN=your-generated-token-here

What Changed? Visual Comparison

Before (v1.x):

  • n8n container
  • postgres container
  • No runners, no isolation

New (v2.0) - Internal:

  • n8n container (with internal runners)
  • postgres container
  • No extra containers

New (v2.0) - External:

  • n8n container (acts as task broker on port 5679)
  • n8n-runner container (connects to n8n via WebSocket)
  • postgres container
  • Auth token for secure communication
  • No Redis (unless you're in queue mode)

Migration Checklist

For internal mode (most people):

  • [ ] Update security environment variables
  • [ ] Add n8n_files volume
  • [ ] Set chmod 600 on config files
  • [ ] Test OAuth integrations
  • [ ] Review Code nodes (especially those using process.env)
  • [ ] Check disk space
  • [ ] Back up everything

For external mode (Python users or high security):

  • [ ] Everything above plus:
  • [ ] Generate token with openssl rand -hex 32
  • [ ] Add N8N_RUNNERS_AUTH_TOKEN to .env
  • [ ] Add n8n-runner service
  • [ ] If using Python: build custom image with Python installed
  • [ ] Verify port 5679 is accessible between containers

Common Issues

"Task runner won't connect"

Check:

  1. Auth token identical in n8n and runner
  2. Broker URI correct: http://n8n:5679 (or your container name)
  3. N8N_RUNNERS_BROKER_LISTEN_ADDRESS=0.0.0.0 in n8n (to accept external connections)

"OAuth stopped working"

You didn't test with N8N_SKIP_AUTH_ON_OAUTH_CALLBACK=false before upgrading.

"File operations failing"

Files aren't in /home/node/.n8n-files. Move them or adjust the path.

"n8n won't start - permission error"

Config files need chmod 600. Windows users: N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=false.

"Unauthorized error from runner"

Tokens don't match. Check your .env.

How Not to Screw This Up

Step 1: Check the migration report

Settings → Migration Report (you need admin access)

Available since v1.121.0. Run it now.

Step 2: Decide your runner mode

Using Python Code nodes? → External mode mandatory Need maximum security? → External mode Everything else? → Internal mode is enough

Step 3: If going external, generate token

bash
openssl rand -hex 32 >> .env

Edit .env:

bash
N8N_RUNNERS_AUTH_TOKEN=your-token-here

Set permissions:

bash
chmod 600 .env

Step 4: Test in staging

bash
N8N_RUNNERS_ENABLED=true
N8N_SKIP_AUTH_ON_OAUTH_CALLBACK=false  
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true

Step 5: Review every Code node

Especially:

  • Anything using process.env
  • All Python Code nodes
  • File operations

Step 6: Test OAuth

All integrations. Manually. No exceptions.

Step 7: Back up everything

Database, workflows, credentials, configs, complete .n8n directory.

Step 8: Help find bugs

Report bugs either in an issue on the repository or in the n8n community forum.

My Take

This is the most significant n8n release since 1.0. The security improvements are necessary.

Most people will be fine with internal mode—it's simple, you just set N8N_RUNNERS_ENABLED=true and you're done. You don't need extra containers, you don't need Redis, you don't need complicated auth tokens.

External mode is only necessary if:

  • You use Python Code nodes (mandatory)
  • You have extreme security requirements
  • You're running queue mode with multiple workers

If you're running n8n in production (like I do every day), take this seriously. Budget real time for testing and migration. Don't just do docker pull and hope for the best.

The good news? After migrating, your instance will be more secure, more stable, and better positioned for future updates.

Resources

Topics

n8nworkflow automationsoftware updatebreaking changesversion 2.0automation tools

Keep reading

Go back to the archive and read more posts.

Browse all posts