From d29c46c2295406dd657485805f4e6e94308bb835 Mon Sep 17 00:00:00 2001 From: Lukas Remis Date: Mon, 14 Sep 2026 21:10:37 +0200 Subject: [PATCH] Docs: DSM firewall blocks container-to-container traffic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Diagnosed on a real Synology: with the DSM firewall enabled, iptables reads -A FORWARD -j FORWARD_FIREWALL <- DSM, first, default deny -A FORWARD -j DEFAULT_FORWARD <- Docker's ACCEPT rules, never reached and bridge-nf-call-iptables=1 sends container-to-container packets through FORWARD. A new connection to postgres:5432 matches no DSM allow rule and is dropped before Docker's own "-i docker-xxx -o docker-xxx -j ACCEPT" is consulted. RELATED,ESTABLISHED is accepted, which is why the port could test open from a throwaway container while every new connection timed out — the symptom looked intermittent and pointed nowhere near the firewall. - docs §8: the diagnosis, the one-line fix (iptables -I FORWARD 1 -i docker+ -o docker+ -j ACCEPT), a boot-up Triggered Task so it survives reboots and firewall reloads, and a verification command. - The migrate failure message now names this cause and the fix, since that is the error an operator actually sees. Nothing in the stack is at fault here; this is host firewall configuration. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Nux3LCu4zP9nih4BRhPyHw --- docker/entrypoint.sh | 9 +++++++ docs/nas-install.md | 60 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index d435da9..2110a7f 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -69,6 +69,15 @@ print( f"Last error: {type(last_error).__name__}: {last_error}", file=sys.stderr, ) +print( + "[entrypoint] If this is a Synology NAS: DSM's firewall filters traffic " + "between containers (FORWARD -j FORWARD_FIREWALL runs before Docker's own " + "rules and denies by default). Fix:\n" + " iptables -I FORWARD 1 -i docker+ -o docker+ -j ACCEPT\n" + "See docs/nas-install.md, 'DSM firewall blocks container-to-container " + "traffic'.", + file=sys.stderr, +) sys.exit(1) PYEOF } diff --git a/docs/nas-install.md b/docs/nas-install.md index cebcefd..77bba9a 100644 --- a/docs/nas-install.md +++ b/docs/nas-install.md @@ -401,7 +401,62 @@ backends, or search ever looks stale, just rebuild it: --- -## 8. Troubleshooting +## 8. DSM firewall blocks container-to-container traffic + +**Symptom:** everything looks healthy, but `migrate` fails with an asyncpg +`TimeoutError`, or any service times out reaching `postgres:5432`. From a +throwaway container the port may even test open — because already-established +flows are allowed while new ones are dropped. + +**Cause:** if Synology's firewall is enabled, DSM inserts its own chain ahead of +Docker's: + +``` +-A FORWARD -j FORWARD_FIREWALL <- DSM firewall, runs first, denies by default +-A FORWARD -j DEFAULT_FORWARD <- Docker's ACCEPT rules, never reached +``` + +With `bridge-nf-call-iptables=1` (the DSM default), traffic between two +containers on the same bridge traverses `FORWARD` — so it hits DSM's firewall, +matches no allow rule (your database port is not in DSM's service list, and +should not be), and is dropped before Docker's +`-i docker-xxx -o docker-xxx -j ACCEPT` is consulted. + +**Fix** — allow Docker bridges to talk to themselves, ahead of the firewall: + +```bash +iptables -I FORWARD 1 -i docker+ -o docker+ -j ACCEPT +``` + +`docker+` matches any interface whose name starts with `docker`, so it keeps +working when the bridge is recreated under a new name. It permits traffic only +*between* Docker bridges — nothing from the LAN or the internet gains access. + +**Make it survive a reboot.** The rule is lost on restart and when DSM reloads +its firewall. Add a *triggered* task in *Control Panel → Task Scheduler → +Create → Triggered Task → User-defined script*, event **Boot-up**, user +**root**: + +```bash +sleep 60 +iptables -C FORWARD -i docker+ -o docker+ -j ACCEPT 2>/dev/null \ + || iptables -I FORWARD 1 -i docker+ -o docker+ -j ACCEPT +``` + +The `sleep` lets DSM's firewall finish loading first; `-C` checks for the rule +so running it twice is harmless. Re-run the same script by hand after any +change to the firewall settings. + +**Verify it:** + +```bash +docker run --rm --network webmail-prod postgres:16 \ + pg_isready -h postgres -p 5432 # expect: accepting connections +``` + +--- + +## 9. Troubleshooting | Symptom | Cause | Fix | |---|---|---| @@ -409,6 +464,7 @@ backends, or search ever looks stale, just rebuild it: | UI loads, no live updates | WebSocket not enabled in the NAS reverse proxy | §5.2 Custom Header → WebSocket | | Login works, logs out on reload | HTTPS terminated upstream but `X-Forwarded-Proto` stripped | Use the NAS reverse proxy as documented; don't strip headers | | "port is already allocated" | 8088 taken by another app | change `WEB_PORT` | +| `migrate` fails with asyncpg TimeoutError | DSM firewall dropping container traffic (§8) | `iptables -I FORWARD 1 -i docker+ -o docker+ -j ACCEPT` | | Mail not syncing | worker stopped, or credentials rejected | `./deploy/webmail.sh logs worker-sync` | | Search finds nothing after a restore | index not rebuilt | `./deploy/webmail.sh reindex --purge` | | App won't start after editing `.env` | malformed `MASTER_KEY` | `./deploy/webmail.sh doctor` | @@ -418,7 +474,7 @@ per container, so they can't fill the volume. --- -## 9. Command reference +## 10. Command reference Run these as root (`sudo -i` first): the env file is mode 600 and the data directories are root-owned.