# Docker Volume Migration Pitfalls Common failures when moving Docker services between hosts. ## Permission denied on data volume (n8n, Jul 10 2026) **Symptom:** Container starts but immediately crashes with `EACCES: permission denied, open '/home/node/.n8n/crash.journal'` **Root cause:** Docker named volumes copied from host A (`scp` the `_data` directory) carry the UID/GID from host A's filesystem. The container on host B runs as a different UID (n8n uses UID 1000, node user). The files are owned by `root` or `ippadmin` from the copy operation, not UID 1000. **Fix:** ```bash VOL=$(docker volume inspect --format "{{.Mountpoint}}") chown -R 1000:1000 "$VOL" docker compose restart ``` **For n8n specifically:** UID 1000 is the `node` user inside the container. Both `n8n_data` and `postgres_data` volumes may need this fix. **Prevention:** After copying volume data to a new host, always check ownership before starting containers: ```bash ls -la /var/lib/docker/volumes//_data/ | head -5 ``` ## Volume data owned by wrong user (general) Any Docker container that writes to a bind-mounted or named volume will have files owned by the container's internal UID. When copying between hosts: 1. Identify the container's UID: `docker run --rm id` 2. Apply: `chown -R : ` 3. Then start the container ## SSL redirect loop after migration (n8n, Jul 10 2026) **Symptom:** n8n returns 308 redirect loop when accessed through Caddy with HTTPS **Root cause:** n8n's `N8N_PROTOCOL` is set to `https` but Caddy terminates SSL. n8n sees the request came via HTTP (from Caddy's proxy) and redirects to HTTPS, creating an infinite loop. **Fix:** Set `N8N_PROTOCOL=http` in `.env` — Caddy handles SSL termination, n8n should trust the proxy. ## Caddy `handle` fallback overrides path routes (Jul 10, 2026) **Symptom:** Browser shows "app1 — /n8n/ /webui/ /litellm/" instead of the app. **Root cause:** A `handle { respond "..." 200 }` at the bottom of the Caddyfile catches ALL requests — including `/n8n/signin` — because Caddy evaluates `handle` blocks before `handle_path` directives. **Fix:** Replace the `handle { respond ... }` with a bare `reverse_proxy` as the catch-all. Path-specific `handle_path` routes take priority over the catch-all reverse_proxy, but NOT over a catch-all `handle` block. **Lesson:** Only use `handle` blocks for specific path prefixes. For a true catch-all, use `reverse_proxy` directly without wrapping in `handle`. Never put `handle { respond }` as a fallback when you also have `handle_path` routes — it overrides them. **Symptom:** `app1.itpropartner.com/n8n/` shows the app but all assets (JS, CSS, images) 404 **Root cause:** `handle_path /n8n/*` correctly strips the prefix before proxying, so n8n sees `/signin` instead of `/n8n/signin`. But n8n's HTML references assets at `/assets/...`, `/static/...` — these paths DON'T match `/n8n/*` and fall through to the default handler. **Fix:** Give the service its own subdomain instead of a sub-path. Path-based routing only works for apps that support a `baseUrl` config option. n8n does not. **Lesson:** Use sub-domain routing (`n8n.itpropartner.com`) for apps without baseUrl support, sub-path routing (`domain.com/app/`) only for apps that support it. ## Let's Encrypt validation blocked by UFW / DNS (Jul 10, 2026) **Symptom:** Caddy log shows `authorization failed: HTTP 400 ... Timeout during connect (likely firewall problem)`. Cert never issues. **Root cause:** UFW default deny blocks HTTP-01 challenge from Let's Encrypt's servers, OR DNS still points to wrong IP (stale SiteGround records during Cloudflare migration). **Fix:** (1) Ensure `ufw allow 80/tcp` and `ufw allow 443/tcp` on the target server. (2) Verify DNS resolves to the correct IP from outside the server: `ping app1.itpropartner.com`. Core's DNS may be stale while your laptop's is fresh — test from an external source. ## n8n Caddy deployment checklist (Jul 10, 2026) When deploying n8n behind Caddy on a new host: 1. `N8N_PROTOCOL=http` — Caddy terminates SSL 2. `N8N_HOST=` — match the public domain 3. `WEBHOOK_URL=https:///` — for webhook callbacks 4. Caddy: `reverse_proxy 127.0.0.1:5678 { flush_interval -1 }` — WebSocket support 5. Caddy: `header { X-Forwarded-Proto https; X-Forwarded-Host {host} }` — proxy headers 6. UFW: allow 80/tcp + 443/tcp for Let's Encrypt validation 7. DNS must resolve to this server's IP BEFORE starting Caddy (otherwise cert issuance blocks on first request)