Skip to content

Custom certificate created in Certificates section has no way to be applied to a Compose service's domain #4704

Closed

Description

@Mubashir-teapot

To Reproduce

  1. Create a Compose application, deploy it successfully
  2. Go to the application's Domains tab, add a domain, set "Certificate Provider" to "Custom"
  3. The form shows a "Custom Certificate Resolver" text field — paste a certificate + private key (e.g. a Cloudflare Origin CA cert) into it and save
  4. Separately, go to the dedicated "Certificates" section in the sidebar, create a new certificate entry with Name / Certificate Data / Private Key (cert and key pasted into their own separate fields this time)
  5. Go back to the domain's HTTPS settings — there is no dropdown or option anywhere to select the certificate just created in step 4
  6. Redeploy the compose service
  7. Visit the domain through Cloudflare (SSL mode: Full Strict) — get Error 526 "Invalid SSL Certificate"
  8. Inspect /etc/dokploy/traefik/dynamic/ on the server — the certificate's files (certificate.yml, chain.crt, privkey.key) exist under dynamic/certificates//, but nothing in the per-app dynamic YAML references them

Current vs. Expected behavior

Current: After creating a certificate via the dedicated Certificates section, there is no UI path to actually apply it to a specific Compose service's domain. The "Custom Certificate Resolver" field on the domain form turns out to expect a Traefik certResolver name (matching an entry under certificatesResolvers in traefik.yml's static config) rather than raw certificate/key content — pasting cert/key data there does nothing useful, and there's no inline indication of this expected format.

Expected: After saving a certificate in the Certificates section, the domain's HTTPS settings should offer a way to select it (the docs at docs.dokploy.com/docs/core/certificates imply this should exist — "select your newly created certificate" — but no such control appears in the domain form for a Compose service). Selecting it should result in Traefik actually using that certificate for the domain's TLS handshake.

Provide environment information

OS: Ubuntu 24.04.3 LTS
Arch: x86_64
Dokploy version: 0.29.8
VPS Provider: <fill in your provider>
Applications: Docker Compose stack — Node.js/Express API, static SPA served via nginx, Next.js app, MySQL, Redis

Which area(s) are affected? (Select all that apply)

Docker Compose

Are you deploying the applications where Dokploy is installed or on a remote server?

Same server where Dokploy is installed

Additional context

Cloudflare was set to Full (Strict) SSL mode, which requires the origin (Traefik) to present a valid certificate — this is why the missing certificate-selection UI became a blocker rather than a cosmetic gap. Worked around by switching Cloudflare to Flexible mode instead, which avoids needing an origin certificate entirely, but that's a workaround, not a fix for the underlying gap.

Will you send a PR to fix it?

Yes

Activity

  1. dosubot commented on Jun 28, 2026

    @dosubot

    Hi @Mubashir-teapot! I'm Dosu and I'm helping the Dokploy team.

    You've identified a real architectural gap. Here's what's happening under the hood:

    The "Certificates" section and "Custom Certificate Resolver" field serve different purposes:

    • The Certificates section creates physical certificate files at /etc/dokploy/traefik/dynamic/certificates/{id}/ with a certificate.yml that Traefik auto-loads via its file provider [1]. These are TLS certificates that Traefik knows about but they're matched to domains via SNI — Traefik will use them automatically if the certificate's CN/SAN matches the requested domain and the router has tls: {} enabled without a specific certResolver.

    • The "Custom Certificate Resolver" field on a domain expects the name of a Traefik certificatesResolver (e.g. "letsencrypt") — not raw cert data, and not the name of a certificate from the Certificates section [2] [3]. For Compose services, this gets injected as a Docker label: traefik.http.routers.{name}.tls.certresolver={value} [3].

    Why your setup fails with Error 526:

    When you set certificate type to "custom" and paste cert/key content into the "Custom Certificate Resolver" text field, that value gets used as a literal resolver name in Traefik's config. Since no such resolver exists, Traefik can't obtain/present a certificate, and Cloudflare's Full (Strict) mode rejects the connection.

    The workaround for now:

    If you've created a certificate in the Certificates section whose CN/SAN matches your domain, you should be able to set the certificate type to "none" on the domain. This sets tls: {} without a certResolver, which lets Traefik fall back to SNI-based certificate selection from its loaded certificate store — i.e., it should pick up the certificate you created in the Certificates section. This is worth testing.

    On the UX gap:

    This was also raised in issue #4610, which proposed replacing the free-text input with a dropdown of registered certificates. That issue was closed without a fix. Your description is clearer about the actual problem — the docs say "select your newly created certificate" but no such selector exists in the domain form for Compose services. A PR adding a dropdown (or at minimum, proper documentation of the expected format) would be a welcome improvement.

    Would you like me to look into the specific code paths for Compose domain label generation to help guide your PR?

    To reply, just mention @dosu.


    Share context across your team and agents. Try Dosu.

    Leave Feedback Learn about dokploy with Dosu Add Dosu to your team

  2. Mubashir-teapot commented on Jun 29, 2026

    @Mubashir-teapot
    Author

    Tested the "None" certificate type workaround it did not resolve the issue. Still getting Error 526 from Cloudflare in Full (Strict) mode.

    Confirmed the certificate itself is loading correctly: its files (certificate.yml, chain.crt, privkey.key) are present under /etc/dokploy/traefik/dynamic/certificates//, and Traefik's file provider does pick those up. The gap is specifically on the domain/router side.

    Separately, I tried to inspect the actual router config Dokploy generates for my Compose service's domains to debug this myself, but couldn't find it anywhere under /etc/dokploy/traefik/dynamic/ — that directory only contains acme.json, dokploy.yml (which is just Dokploy's own dashboard router), middlewares.yml, and the certificates folder. No per-app routing file exists there for a Compose service's domains.

    If Dokploy generates these routes as Docker labels on the service containers (via the docker provider in traefik.yml) rather than as dynamic files, that's fine, but it means there's currently no way for a user to see or audit the actual router/TLS config being applied to their domain through the filesystem — only by manually docker inspecting each container's labels. Could someone confirm whether that's the intended mechanism for Compose services? If so, it'd be worth documenting, since right now there's no visibility into what Dokploy is actually telling Traefik for a given domain, which makes debugging issues like this one (and #4610) much harder than it needs to be.

    This also isn't just a UX papercut it's a real security gap. Without a working path to apply a custom certificate, Full (Strict) Cloudflare↔origin encryption isn't achievable through the UI at all, forcing a fallback to Flexible mode (unencrypted Cloudflare↔origin hop) as the only way to get HTTPS working end to end.

    On the offer to dig into this myself: I've been reviewing how my own stack manages Docker/Traefik config while debugging this, so I'm comfortable working through the actual mechanism if pointed at the relevant source — happy to review the real Compose domain/label-generation code and send a PR if that's useful. I did try manually editing Traefik's config directly on my server to work around this, but that caused its own issues, so I've held off going further down that path solo. Rather not fight this blind — if someone can point me at the right files, I'll take a real pass at fixing it; otherwise happy to hand this off and let the team take it from here.

  3. dosubot commented on Jun 29, 2026

    @dosubot

    @Mubashir-teapot — you're correct: Compose service routes are generated entirely as Docker labels on the service containers, not as dynamic YAML files. The only file-based dynamic configs are dokploy.yml (dashboard), middlewares.yml, acme.json, and the certificates folder. For Compose services, Traefik discovers routing via its Docker provider, so docker inspect is indeed the only way to audit the applied config. That's the intended mechanism, though I agree it lacks visibility.

    Here are the relevant source files for a PR:

    Primary file: packages/server/src/utils/docker/domain.ts

    • createDomainLabels() (lines 255–348) — generates all Traefik labels for a domain, including TLS handling [1]
    • addDomainToCompose() (lines 134–237) — injects those labels into the compose spec [2]
    • writeDomainsToCompose() (lines 109–133) — encodes the modified compose file and returns the deployment command [3]

    Integration point: packages/server/src/utils/builders/compose.ts at line 26 calls writeDomainsToCompose() [4].

    Regarding certificateType === "none": The code at lines 340–344 does set traefik.http.routers.{name}.tls=true when HTTPS is enabled [5]:

    } else if (certificateType === "none" && https) {
        labels.push(`traefik.http.routers.${routerName}.tls=true`);
    }

    This should allow Traefik to use SNI-based matching against its loaded certificate store. If you're still getting 526, it's worth verifying:

    1. The CN or SAN on your Cloudflare Origin CA cert matches the domain exactly (wildcard or FQDN)
    2. Traefik is actually loading the cert (check docker logs on the Traefik container for TLS-related messages or hit the Traefik API at /api/rawdata to see loaded certificates and router configs)

    If the cert is loaded and SNI matching just isn't working, the fix in your PR might need to add explicit tls.domains or tls.options labels pointing to the certificate, or introduce a proper dropdown that maps to the certificate's path directly — rather than relying on Traefik's implicit SNI matching.

    There are also tests at apps/dokploy/__test__/compose/domain/labels.test.ts if you want to validate behavior changes.

    To reply, just mention @dosu.


    Docs are dead. Just use Dosu.

    Leave Feedback Ask Dosu about dokploy Share Dosu with your team

  4. Siumauricio commented on Jun 29, 2026

    @Siumauricio
    Contributor

    Hey, I added some suggestions on the UI, If you want to use a cloudflare certificate you can follow this guide https://docs.dokploy.com/docs/core/domains/cloudflare

  5. Mubashir-teapot commented on Jun 29, 2026

    @Mubashir-teapot
    Author

    Thanks both — tried the Cloudflare guide approach again (Certificate type none on the domain, matching cert in the Certificates section) and confirmed via direct SNI check against the server (bypassing Cloudflare entirely) that Traefik is still presenting TRAEFIK DEFAULT CERT, not the uploaded one:

    $ nmap --script ssl-cert -p 443
    | ssl-cert: Subject: commonName=TRAEFIK DEFAULT CERT
    | Subject Alternative Name: DNS:.traefik.default
    So the SNI-based fallback @dosu described (tls=true with no certResolver → Traefik should match against its loaded cert store) isn't kicking in for me, even though the cert files are confirmed present and well-formed on disk (certificate.yml, chain.crt, privkey.key under dynamic/certificates//). Still getting 526 from Cloudflare in Full (Strict).

    @Siumauricio I saw #4705 — is that merged into a build I can pull and test, or still pending? Want to confirm whether this is purely the missing-selector UX gap your PR addresses, or a separate bug in the SNI fallback matching itself. If the fallback is genuinely not matching despite a valid CN/SAN, that's a second issue beyond just "no UI to pick a cert" — happy to dig into createDomainLabels()/addDomainToCompose() and the Traefik docker-provider side to confirm which one it is, and send a PR once I know which.

  6. dosubot commented on Jun 29, 2026

    @dosubot

    @Mubashir-teapot — I dug into this further and I think you're right that there's a real bug here beyond just the missing UI selector. Here's what I found:

    PR #4705 adds help text to the domain form confirming the expected behavior: when "None" is selected, Traefik should automatically match a loaded certificate via SNI [1]. It's a docs/UX improvement, not a fix for the SNI matching itself.

    Why SNI matching may be failing:

    The certificate.yml generated by Dokploy contains only file paths — no domain/SAN hints [2]:

    tls:
      certificates:
        - certFile: /path/to/chain.crt
          keyFile: /path/to/privkey.key

    Traefik must parse the actual .crt file to extract CN/SAN for matching. For this to work, the certificate's CN/SAN must exactly match the domain in the router's Host() rule. A few things to check:

    1. Verify your cert's CN/SAN:

      openssl x509 -in /etc/dokploy/traefik/dynamic/certificates/<id>/chain.crt -text -noout | grep -A1 "Subject Alternative Name"
    2. Verify the exact Host() rule on the container:

      docker inspect <your-compose-container> | grep "traefik.http.routers"
    3. Compare them exactly — Compose services don't normalize domains (no lowercase conversion), so if there's any case mismatch between the Host() rule and the cert's SAN, SNI matching will silently fail and Traefik falls back to its default cert [3].

    4. Cloudflare Origin CA wildcards — if your cert is *.example.com but the Host() rule is example.com (bare domain, no subdomain), that won't match either.

    Also worth checking Traefik's debug logs (--log.level=DEBUG) — it should log which certificates it's loading from the file provider and any SNI matching attempts.

    If the CN/SAN and Host() rule match correctly and it still falls back to the default cert, that would confirm a bug in how Traefik processes file-provider certificates for Docker-provider routers — which would be a separate issue from what #4705 addresses.

    To reply, just mention @dosu.


    Share context across your team and agents. Try Dosu.

    Leave Feedback Learn about dokploy with Dosu Add Dosu to your team

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions