Dokploy / Dokploy/dokploy

Bug: HTTPS certificate never issued after DNS correction — Traefik ACME retry gap

Open
#3,724 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
37.4k
Forks
3k
Avg merge
1d 3h
Merged PRs (30d)
73

Description

Bug: HTTPS certificate never issued after DNS correction — Traefik ACME retry gap

What happens

You add a domain in Dokploy with HTTPS enabled (Let's Encrypt). Your DNS A record
happens to point to the wrong IP at that moment — a common setup mistake (migration
in progress, typo in the IP, DNS not yet propagated, etc.).

You fix the DNS. You verify with dig that propagation is complete. You go back to
Dokploy, edit the domain, click Save. Nothing happens. The certificate is never issued.
You wait an hour. Still nothing. The only workaround is to SSH into the server and run
docker restart dokploy-traefik — then the cert appears in 30 seconds.

The core problem: once Traefik fails an ACME challenge, it never retries on its own,
and Dokploy has no mechanism to tell it to try again.


Why this happens — step by step

Here's the exact chain of events, traced through the code:

  1. Domain creation (packages/server/src/services/domain.ts:16-42)
    manageDomain() writes a Traefik YAML config with:

    • Router rule: Host(`app.example.com`)
    • Service URL: http://appname:3000 (Docker internal name)
    • TLS: certResolver: "letsencrypt"
  2. ACME HTTP-01 challenge starts
    Traefik asks Let's Encrypt for a challenge token. Let's Encrypt calls back to
    http://app.example.com/.well-known/acme-challenge/<token>.
    Since DNS points to IP1 (wrong server), the callback hits IP1 → challenge fails.

  3. Traefik gives up — by design
    Traefik deliberately does not retry failed ACME challenges to avoid burning
    through Let's Encrypt rate limits. This is a conscious design choice:
    traefik/traefik#9405.
    Failed domains are not stored in acme.json — they simply don't exist
    anywhere. No cert, no record, no scheduled retry.

  4. User fixes DNS, then saves domain in Dokploy UI
    The update mutation (apps/dokploy/server/api/routers/domain.ts:119-176)
    calls manageDomain() at line 164, which regenerates the Traefik config.
    But the router rule is still Host(`app.example.com`) and the service
    is still http://appname:3000the file content is byte-for-byte identical.
    Traefik's file watcher sees no change and does nothing.

  5. The domain is stuck forever
    Traefik won't retry the ACME challenge on its own. The config file didn't change
    so the file watcher won't trigger. There is no "retry certificate" button in the
    domain UI. The user has no recourse from within Dokploy.

  6. Why docker restart fixes it
    On startup, Traefik compares all active routers (with certResolver set)
    against its acme.json. Any domain present in the config but absent from
    acme.json triggers a fresh ACME attempt. Since DNS now points to the
    correct server, the HTTP-01 challenge succeeds.


Steps to reproduce

  1. Deploy Dokploy on Server B (IP2)
  2. Create an application, add domain test.example.com with HTTPS (Let's Encrypt)
  3. Have DNS test.example.com → IP1 (any IP that isn't Server B)
  4. Check Traefik logs: docker logs dokploy-traefik 2>&1 | grep -i acme
    unable to obtain ACME certificate for domains
  5. Fix DNS: test.example.com → IP2, confirm with dig +short test.example.com
  6. Visit https://test.example.com → SSL error (no certificate)
  7. Edit the domain in Dokploy UI, click Save → still no certificate
  8. Wait 1 hour → still no certificate
  9. docker restart dokploy-traefik → certificate issued within 30 seconds

Suggested fix

The infrastructure to solve this already exists in the codebase.
reloadDockerResource("dokploy-traefik", serverId) is implemented in
packages/server/src/services/settings.ts:283-312 and exposed via the
reloadTraefik mutation in apps/dokploy/server/api/routers/settings.ts:126-136.
It handles both Swarm (docker service update --force) and standalone
(docker restart) modes.

The fix: in the domain update mutation (domain.ts:164), after manageDomain(),
call reloadDockerResource("dokploy-traefik", serverId) when the domain uses
Let's Encrypt. This follows the exact same pattern already used by reloadTraefik
in the settings router.

Optionally, calling validateDomain() at domain creation time and warning the
user when DNS doesn't point to the server IP would prevent the initial ACME
failure entirely.

Environment

  • Traefik version: 3.6.7 (hardcoded in traefik-setup.ts:23)
  • ACME config: HTTP-01 challenge only (traefik-setup.ts:309)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read the domain update mutation in apps/dokploy/server/api/routers/domain.ts around lines 119-176, then compare its manageDomain() call with reloadTraefik in apps/dokploy/server/api/routers/settings.ts and reloadDockerResource() in packages/server/src/services/settings.ts:283-312. The fix is complete when updating a Let's Encrypt domain triggers a Traefik reload and a previously failed certificate is issued after DNS correction.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, typescript
Domain
devops, infrastructure
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.