# n8n Migration: WEBHOOK_URL + Workflow Webhook Fix

When migrating n8n from one domain to another, two things MUST be updated:

## 1. Environment Variable — .env

```
WEBHOOK_URL=https://NEW_DOMAIN/
```

This controls the base URL for all webhook nodes. Restart n8n after changing.

## 2. Existing Workflow Webhook URLs — PostgreSQL

n8n stores webhook URLs in the `nodes` JSON column of `workflow_entity`. After changing `WEBHOOK_URL`, existing workflows still have old URLs baked in. Update them directly:

```sql
UPDATE workflow_entity 
SET nodes = regexp_replace(
    nodes::text, 
    'https://old\.domain\.com', 
    'https://new.domain.com', 
    'g'
)::json;
```

Then restart n8n to reload workflows from the database.

## Full Migration Steps

1. `docker compose down` on old server
2. `tar czf n8n-data.tar.gz` Postgres volume + n8n data volume
3. `scp` to new server
4. Update `.env`: `N8N_HOST`, `N8N_PROTOCOL`, `WEBHOOK_URL` to new domain
5. `docker compose up -d` on new server
6. Fix volume permissions: `chown -R 1000:1000` on n8n data volume (node user inside container = UID 1000)
7. Update workflow webhook URLs via SQL above
8. Restart n8n: `docker compose restart n8n`
9. Verify: `SELECT nodes::text LIKE '%old.domain.com%' FROM workflow_entity WHERE name LIKE '%workflow_name%';` → should return `f`

## Common Pitfall

Sub-path routing (e.g., `app1.domain.com/n8n/`) breaks n8n because its SPA uses absolute asset paths (`/assets/`, `/static/`). Always use a dedicated subdomain (`n8n.domain.com`).
