Self-hosting
Troubleshooting Self-Hosted n8n: Webhooks, HTTPS & Crashes
Fix the classic self-hosted n8n problems: webhooks that don't fire, 'Connection lost' banners, secure cookie errors, crash loops, and a slow editor.
Self-hosted n8n fails in remarkably predictable ways. The same six or seven problems account for nearly every “help, my instance is broken” thread — and almost all of them are configuration, not bugs. This guide is organized by symptom: find yours, apply the fix, get on with your day.
First, though: three commands that diagnose 90% of everything.
The universal first moves
cd ~/n8n
docker compose ps # is everything actually running?
docker compose logs --tail=100 n8n # what is n8n complaining about?
df -h # is the disk full? (you'd be surprised)
Read the log lines around the first error, not just the last one — the root cause usually appears once, followed by pages of downstream noise. For anything subtle, raise verbosity with N8N_LOG_LEVEL=debug (see the env vars guide) and reproduce the problem.
”My webhook doesn’t fire” (or: works in test, dead in production)
The most common problem in all of self-hosted n8n, and it’s almost always one of four things:
1. Test URL vs production URL. The Test webhook URL (/webhook-test/...) only works while you’re in the editor with “Listen for test event” active. External services must call the production URL (/webhook/...), which only exists while the workflow is Active (the toggle, top-right). Sending Stripe events to a test URL and wondering why nothing happens is a rite of passage.
2. WEBHOOK_URL is wrong or missing. Behind a reverse proxy, n8n can’t know its public address — it will happily register http://localhost:5678/webhook/... with external services unless you tell it otherwise:
WEBHOOK_URL=https://n8n.yourdomain.com/
Check what n8n thinks its webhook URL is by opening any Webhook node — the displayed URL should be your public domain. If it shows localhost or an internal name, this is your fix.
3. The request never reaches the server. Test from outside your network:
curl -i https://n8n.yourdomain.com/webhook/your-path
A timeout means DNS or firewall (is the A record right? does sudo ufw status allow 443?). A Caddy error page means the proxy is up but can’t reach n8n (docker compose ps — is the n8n container healthy?). A 404 from n8n means the request arrived but no active workflow owns that path — see points 1 and 2.
4. The workflow fired but failed. Check Executions in the sidebar. A webhook that triggers a failing workflow looks identical, from the caller’s side, to one that never fired. Save errors so you can see them: EXECUTIONS_DATA_SAVE_ON_ERROR=all.
The “Connection lost” banner in the editor
The editor keeps a live push connection (WebSocket) to the server for execution updates. When a proxy in the middle can’t carry it, you get the yellow banner — and manual executions that seem to hang forever.
- Caddy: handles WebSockets automatically; if you followed our guide, you’ll likely never see this. If you do, your problem is usually a second proxy (Cloudflare proxied DNS, a corporate proxy) in front of Caddy.
- nginx: needs the upgrade headers spelled out on the n8n location block:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
- Cloudflare: WebSockets must be enabled (Network settings), and aggressive timeout settings on the free tier can drop long-lived connections — many self-hosters simply set the n8n subdomain to “DNS only” (grey cloud) and let Caddy handle TLS.
”Your n8n server is configured to use a secure cookie…”
You opened the editor over plain HTTP (usually http://SERVER_IP:5678 while testing) and n8n refused to hand out its auth cookie. Two paths:
- Right fix: access it the way production should work — through the proxy, over HTTPS, with
N8N_PROTOCOL=httpsandN8N_PROXY_HOPS=1set so n8n trusts the forwarded scheme. - Wrong fix that everyone is tempted by:
N8N_SECURE_COOKIE=false. This sends your session cookie in cleartext; it’s acceptable for a laptop experiment, never for an internet-facing instance. If you “need” it, your proxy config is the actual bug. (More on why in the hardening guide.)
”Credentials could not be decrypted”
The running N8N_ENCRYPTION_KEY doesn’t match the key that encrypted the credentials in your database. This appears after migrations, restores, or moving between servers.
Do not re-save credentials or delete anything — the data is intact, you just have the wrong key. Find the original: your password manager, an old .env backup, or the auto-generated one inside the old data volume:
docker compose exec n8n cat /home/node/.n8n/config
Set it, restart, and everything decrypts again. Then go read the backup guide, because this error is the one that becomes permanent when the old server is already gone.
n8n won’t start / restart loop
docker compose ps shows n8n restarting; the logs (docker compose logs n8n) tell you which flavor:
- Database connection refused: Postgres isn’t ready or the credentials/hostname are wrong. Remember
DB_POSTGRESDB_HOSTis the Compose service name (postgres), notlocalhost. Our stack’sdepends_on: condition: service_healthyprevents the race — check that it’s still there. - Migration errors right after an update: you may have jumped too many versions, or downgraded against a migrated database. The update guide’s rollback section covers both directions.
- Exit code 137: the container was OOM-killed. Check
docker statsandfree -h. On a 1–2 GB server, add swap as a stopgap and prune executions (next section); the real fix is more RAM. - Disk full (
no space left on device):df -h, then reclaim space —docker system prunefor old images, and see the next section for the usual culprit.
Editor slow, database enormous, backups bloated
Almost always execution history. n8n saves every run’s full data by default, forever, and busy instances accumulate gigabytes. Turn on pruning:
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168 # keep 7 days
For high-frequency workflows, additionally disable saving successful runs in that workflow’s own settings. Full policy discussion in the env vars guide. After enabling pruning on a badly bloated Postgres, reclaim the space with VACUUM once the old rows are gone.
Schedules fire at the wrong hour
Your Schedule node runs on GENERIC_TIMEZONE, and unset it means UTC. Set both timezone variables and recreate the container:
GENERIC_TIMEZONE=Europe/Madrid
TZ=Europe/Madrid
If the offset is exactly one hour and appeared unannounced: daylight saving happened, and half your team’s “9:00” expectations changed while UTC didn’t.
”Request Entity Too Large” on webhooks
Payloads have two gatekeepers: n8n (N8N_PAYLOAD_SIZE_MAX, in MB — default 16) and your proxy. Caddy is permissive by default; nginx caps request bodies at 1 MB unless you raise client_max_body_size. If you’re receiving file uploads, raise both, and set N8N_DEFAULT_BINARY_DATA_MODE=filesystem so binaries stay out of your database.
Still stuck?
Work the ladder: exact error into the logs → this site’s guides → the n8n community forum (search first — your error is almost certainly there) → a minimal reproduction (fresh workflow, same error?). And once you’ve fixed it, spend the ten minutes making the next incident boring: tested backups, pinned versions, and error-notification workflows so problems page you before they page your users.