Step 0: Start with the logs
Every Watchtower troubleshooting session starts the same way:
docker logs watchtower --tail 50
Watchtower logs each poll cycle: what it checked, what it skipped, and why. Common messages and what they mean:
- "client version 1.25 is too old" — you are running the archived
containrrr/watchtowerimage against a modern Docker daemon. This one has a dedicated fix: fixing the "Client Version 1.25 Is Too Old" error. On Docker 29 specifically, see the Docker 29 checklist. - "No new images found" — checks succeeded; upstream tags genuinely have no newer digest. Jump to the tag-pinning section below.
- "Skipping container ... no matching label" —
WATCHTOWER_LABEL_ENABLEopt-in mode is on. - "unauthorized" / "authentication required" — registry credentials are missing or expired.
- Nothing at all for hours — the schedule may not be firing (see the schedule section), or the container may be crash-looping; check
docker psfor restarts.
Step 1: Force a debug run
Instead of waiting for the next scheduled check, trigger one update cycle immediately with full debug output:
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock nickfedor/watchtower --run-once --debug
The --run-once flag performs a single check-and-update cycle then exits; --debug logs every decision: which containers were evaluated, which images were pulled, which digests matched, and which containers were skipped and why. This single command resolves most mysteries — if a container is skipped, the debug line usually says exactly why.
Tip: use the same image name in the debug command as your running Watchtower container. If you still run the archived containrrr/watchtower, swap the image in the command above — and then migrate to the fork, since the old image cannot work on modern engines.
Step 2: Schedule vs poll interval confusion
Watchtower offers two timing mechanisms, and they are mutually exclusive:
WATCHTOWER_POLL_INTERVAL— check every N seconds. Example:86400checks once per day.WATCHTOWER_SCHEDULE— a 6-field cron expression where the first field is seconds. Example:"0 0 4 * * *"runs at 04:00:00 daily.
The classic mistakes:
- Setting both. When both are present, the schedule wins and the interval is ignored. If you set
WATCHTOWER_POLL_INTERVAL=300expecting 5-minute checks but also have a daily schedule from an old config, you will wait a full day between checks. - Five-field cron. Standard cron has 5 fields; Watchtower needs 6 (seconds first).
"0 4 * * *"is not a valid Watchtower schedule and may never fire or fire at an unexpected time. Always write"0 0 4 * * *". - Timezone. The schedule is evaluated in the timezone set by the
TZvariable (default UTC). A 4 AM schedule in UTC is a different wall-clock time than 4 AM local. SetTZexplicitly if the timing matters to you. - Assuming it already ran. After a restart, the first scheduled run happens at the next cron hit — not immediately. Use
--run-oncefor an immediate check instead of waiting.
Step 3: Check for the monitor-only label
This one is silent by design. If a container carries the label com.centurylinklabs.watchtower.monitor-only=true — or Watchtower itself runs with WATCHTOWER_MONITOR_ONLY=true — Watchtower will check for updates and log what it would do, but never recreate the container. From the outside this looks identical to "not updating."
Inspect a container's labels:
docker inspect myapp --format '{{json .Config.Labels}}'
And check Watchtower's own environment:
docker inspect watchtower --format '{{json .Config.Env}}'
Remove the monitor-only label (or variable) from anything you actually want updated. Monitor-only mode is meant for staging visibility, not for containers you expect to roll forward. The complete label reference lives at Watchtower container labels.
Step 4: Check image tag pinning
Watchtower re-pulls the exact tag the container was created from and updates only when the digest changes. Three pinning traps:
- Pinned version tags. A container created from
postgres:16.2will never move to16.3— Watchtower only refreshes16.2itself. If you want rolling updates, use a floating tag like:latestor a moving branch tag. - Digest-based containers. A container created from
image@sha256:...has no tag to re-pull; Watchtower skips it. - Stale tag in the running container. If you changed the tag in your compose file but never recreated the container, the running container still carries the old tag. Run
docker compose up -dso reality matches the file.
Also enable WATCHTOWER_CLEANUP=true so old images are removed after updates — without it, disk pressure can cause pulls to fail and updates to stall halfway. Notifications via WATCHTOWER_NOTIFICATION_URL are worth setting up too, so a failed pull pages you instead of failing silently (see Watchtower notifications).
Step 5: Check the label opt-in flag
With WATCHTOWER_LABEL_ENABLE=true, Watchtower updates only containers labeled com.centurylinklabs.watchtower.enable=true. Everything unlabeled is skipped without error. This flag often survives in old compose files or run commands long after the reason for it is forgotten.
Either add the enable label to the containers you want managed, or drop the variable to return to the default "manage everything except explicitly disabled" behavior. The disable label com.centurylinklabs.watchtower.enable=false is also worth grepping your compose files for — one copy-pasted service block can exempt a container you expected to be updated.
Step 6: Check private registry credentials
If public Docker Hub images update but private registry images do not, credentials are the problem. Watchtower authenticates using the host's Docker config (~/.docker/config.json); symptoms in --debug output are unauthorized or authentication required errors during image checks.
- Re-run
docker loginagainst the private registry on the host — tokens and passwords expire, often silently. - Confirm the config file is readable by the Watchtower container (permissions, correct home directory, not a fresh host where nobody logged in).
- If Watchtower runs in an environment that cannot see the host's Docker config, mount it explicitly:
-v ~/.docker/config.json:/config.json.
Step 7: Check Docker socket access
Watchtower needs /var/run/docker.sock mounted read-write. If the volume mount is missing, points at the wrong path (common on Docker Desktop for Mac/Windows or rootless Docker, where the socket lives elsewhere), or the container runs as a user without socket permissions, Watchtower cannot list containers at all — and the logs will show connection errors rather than update activity.
docker inspect watchtower --format '{{json .Mounts}}'
Verify the socket mount exists and matches the socket your working docker CLI uses (docker context ls and docker info tell you where that is).
Quick-reference summary
| Symptom | Likely cause | Fix |
|---|---|---|
| "client version 1.25 is too old" in logs | Archived containrrr image on a modern daemon | Switch to nickfedor/watchtower |
| Nothing updates, long silences in logs | Schedule misconfigured / waiting on cron | Fix 6-field cron or TZ; use --run-once to test |
| Logs say "would update" but nothing changes | Monitor-only label or variable | Remove monitor-only label / WATCHTOWER_MONITOR_ONLY |
| "No new images found" but you know there are updates | Pinned tag, digest, or stale container tag | Use a floating tag; recreate the container |
| Specific containers never touched | WATCHTOWER_LABEL_ENABLE opt-in, or disable label | Add enable label or remove the variable |
| Private registry images fail; Hub works | Expired/missing credentials | docker login; verify config.json visibility |
| Connection errors to the daemon | Socket mount missing or wrong path | Mount the correct docker.sock |
For Docker 29-specific issues, the dedicated Docker 29 checklist goes deeper on engine-version breakage. If you are considering alternatives after repeated trouble, Watchtower vs Diun compares the notification-focused alternative, and Watchtower alternatives lists more options. For setup from scratch, start with how to install Watchtower and the compose setup guide.