Domains, DNS & SSL
Fix a custom domain that won't verify, DNS that hasn't propagated, an SSL certificate stuck on Provisioning, and the external-ingress (TXT-only) case.
Adding a custom domain has two moving parts: the DNS records you paste at your registrar, and the verification step where Openship looks those records up and confirms them. Almost every domain problem is really one of those two records not resolving the way Openship expects. This page walks through the exact messages you'll see and what each one means.
If you haven't added a domain yet, start with the custom domains guide — this page is for when something is already stuck.
The two records, in one sentence
Every custom domain needs a routing record (an A or CNAME that points the name at your app) and a
TXT ownership record at host _openship-challenge (a secret code that proves the domain is yours).
Verify checks both.
Verification keeps failing
Symptom / what you see
You click Verify and it doesn't turn green. A toast titled "Verification failed" appears, and its body is the exact reason Openship returned — one or both of:
A record not pointing to server for app.example.com(self-hosted) orCNAME record not found for app.example.com(Openship Cloud)TXT record _openship-challenge.app.example.com must equal "…"
(If Openship can't produce a specific reason it falls back to the generic "{hostname} could not be verified yet.")
From the API, POST /api/domains/:id/verify returns 422 with cnameVerified and txtVerified flags that
tell you which check is still failing.
What it means. Openship ran two independent lookups and at least one came back wrong. The message names the record that failed, so you never have to guess. Fix whichever record is mentioned — the other one is already fine.
| Message you see | Which record | What's wrong |
|---|---|---|
A record not pointing to server for … | Routing (A) — self-hosted | The A record is missing, or points at a different IP than your server. |
CNAME record not found for … | Routing (CNAME) — Openship Cloud | The CNAME is missing or doesn't point at the Cloud target. |
TXT record _openship-challenge.… must equal "…" | Ownership (TXT) | The _openship-challenge TXT record is missing, or its value doesn't match the secret code. |
Why the API says “cnameVerified” even on a self-hosted A record
The verify response reports the routing check as cnameVerified regardless of whether your project uses an
A record (self-hosted) or a CNAME (Cloud). It's the same "does the routing record point the right way?"
check — only the record type differs. txtVerified is always the ownership TXT.
If the routing record is failing
How to fix
- Open your project's Domains tab and reopen the DNS Records panel for the domain (or run
openship domain records <domainId>). - Compare the routing row against what's actually at your registrar. Self-hosted expects an
Arecord with host@pointing at your server's public IP; Cloud expects aCNAMEwith host@pointing at the Openship Cloud target. - Fix any mismatch — copy the value from Openship, don't retype it. Delete any old, conflicting record for the
same name first (a stale
A/CNAMEleft in place is a common cause). - Wait for the change to take effect, then click Verify again.
Self-hosted: the IP has to be reachable
The A record must resolve to the same public IP as the server this project is deployed on. If your server
sits behind a NAT or a proxy that changes its address, the lookup won't match and verification will keep
failing — point the record at the address the outside world actually sees.
If the TXT ownership record is failing
How to fix
- At your registrar, add (or correct) a TXT record with host
_openship-challenge— Openship looks it up at_openship-challenge.<your-domain>. - Set its value to the exact string from the DNS Records panel. Use the Copy button; a single extra space or a missing character will fail the match.
- Save, wait for propagation, and click Verify again.
Already verified?
If you re-run Verify on a domain that's already done, Openship simply replies "Already verified" and changes nothing — that's expected, not an error.
DNS hasn't propagated yet
Symptom / what you see
The records look correct at your registrar, but Verify still reports the routing or TXT record as not found — sometimes minutes after you saved them.
What it means. DNS isn't instant. After you save a record, it has to spread across the internet's caches before Openship's lookup can see it. As the DNS Records panel itself notes: "DNS changes can take up to 48 hours to propagate globally." Usually it's a few minutes; occasionally it's much longer.
How to fix
- Double-check there are no typos in the Host field and no leftover conflicting record for the same name.
- Wait. Save the records, give it several minutes, then click Verify again — re-clicking once DNS resolves is the reliable path.
- Openship's enabled-by-default Domain DNS verification job runs every 13 minutes. Once a pending domain is at least 10 minutes old, the job retries verification and certificate issuance automatically. This also runs in Openship Desktop against the project's connected deployment server. Clicking Verify is the faster, explicit retry; it is not required for the normal background lifecycle.
What the first deployment does
A new custom domain starts Pending, so its initial deployment creates the HTTP route and prepares the server's SSL toolchain, but deliberately does not open an ACME order yet. Certificate issuance begins only after verification succeeds, either from the scheduled job or from Verify.
Confirm propagation yourself
To check what the world currently sees, look the record up directly — e.g. dig TXT _openship-challenge.app.example.com
or dig A app.example.com. If your own lookup doesn't return the new value yet, Openship won't see it either;
that's a propagation delay, not an Openship problem.
SSL stuck on "Provisioning" (or no certificate issued)
Symptom / what you see
The domain is Verified, but the SSL pill stays on Provisioning instead of moving to Active —
so https:// doesn't work yet.
What it means. On a self-hosted or Desktop-connected server, Verify obtains the certificate before reporting success and rewrites the existing route with TLS. On Openship Cloud, verification can start certificate issuance in the background. Provisioning means issuance is still in progress, its result has not been observed yet, or the first attempt failed.
How to fix
- Give it a moment, then open the domain's ⋯ menu and click Recheck SSL. This is a read-only check (no certificate is re-issued, no rate limit is spent) that flips the status to Active as soon as the certificate is actually in place.
- If Recheck reports "No valid certificate found for {hostname} yet. If you just deployed, give Let's Encrypt a moment, then recheck." — the certificate genuinely isn't there yet. Wait a little longer and recheck again.
- The same scheduled domain job retries incomplete issuance with backoff. If you want an immediate retry, click Renew SSL; any real failure reason (DNS, blocked ports, rate limit, ACME outage) surfaces on that action.
Renewing certificates
Once a certificate is Active you normally never touch it again. Renewal targets certificates within about two weeks of expiry: on Openship Cloud, and on self-hosted setups where your proxy or edge (Traefik, Caddy, a load balancer) owns TLS, this is handled for you; a self-hosted operator can also point a scheduled job at the renew endpoint. Renew SSL is the manual fallback if a certificate ever lapses.
“Recheck / Renew SSL” errors that the domain must be verified
SSL actions require a verified domain — attempting them on a pending one returns "Domain must be verified before SSL can be managed". Finish verification first (above), then manage the certificate.
Verify should not require a redeploy
The first deployment prepares certbot even while the domain is pending. After a successful Verify, the certificate provider re-registers the existing route with TLS, so no redeploy is expected. If a current deployment only starts serving HTTPS after redeploying, preserve both the deployment log and the Verify log; that indicates a separate edge/route reconciliation failure.
External ingress: your edge terminates TLS (TXT only)
Symptom / what you see
Your app already sits behind a Cloudflare Tunnel, a load balancer, or another reverse proxy that handles
HTTPS — but Openship keeps asking for an A/CNAME and tries to issue its own certificate, which you don't
want.
What it means. By default Openship assumes it's the edge: it wants the domain to resolve to it and it issues the certificate. When your edge terminates TLS, that's the wrong model — Openship should only confirm you own the name and stay out of the certificate business.
How to fix
- When adding the domain, turn on External ingress & TLS. As the toggle explains: "TLS terminates
upstream (Cloudflare Tunnel, load balancer). Verify by TXT only — the domain need not point at this server,
and we won't issue a certificate." From the API, pass
"externalIngress": trueonPOST /api/domains. - Add only the
TXTrecord with host_openship-challenge— there's no routing record to add, because your edge already controls where the name points. - Click Verify. Openship checks the TXT alone and confirms with "Domain verified — TLS is handled by your external ingress; no certificate is issued here."
What the SSL pill shows
An external-ingress domain reads External TLS for its SSL status — that's correct and final. Openship issues nothing and never runs certbot for it; your edge owns the padlock. Recheck SSL and Renew SSL don't apply.
Turn it on only if you mean it
If your edge does not handle HTTPS, leave External ingress off — otherwise the domain will verify but serve plain HTTP, with no certificate from anyone. When in doubt, leave it off and let Openship issue the certificate.
Still stuck?
One more thing to check: the hostname itself
If Add domain is rejected before you even reach verification, the hostname failed a basic check — e.g.
Domain "app.example.com" is already in use (it's attached elsewhere) or
"…" is not a valid public hostname. (a bare IP, localhost, or a single-label name). Free *.opsh.io-style
subdomains aren't added here either — those live in the project's public endpoints, not as custom domains.
Custom domains guide
The full add → point DNS → verify → HTTPS walkthrough, click by click.
Domains API reference
Every domain endpoint, the preview/verify responses, and the cnameVerified / txtVerified flags.
Project Domains tab
Where domains, verification status, and the SSL pill live in the dashboard.
Deployment & build failures
The build went red — here's how to read the failure and fix the usual causes, from a wrong build command to a compose deploy that needs a decision.
GitHub connection & repos
Fix an expired or unauthorized GitHub connection, a private repo that won't import, a public repo that should just work, and auto-deploy webhooks that never fire.