docs
/
AI skills

Connect a custom domain to a site

Put a site on the owner's own domain and make it answer — the connect in Site Config, the DNS records that really work, the certificate — change or remove it, buy a domain or edit its DNS in Domains, and when it doesn't open, read what's wrong and what Repair Site Domain does and doesn't fix.

A domain opens a site only when four things are true. Each has its own owner and its own failure:

#WhatWhere it livesSet byWhen it's missing
1DNS sends the name to the platformThe owner's DNS host (or Home › Domains, for a domain bought there)The ownerdig shows another address, or nothing. The browser can't connect, or opens the old host.
2A route and a certificate for the name on the platform's edgeA Kubernetes ingress named <domain-with-dashes>-ingressConnect (and Repair)The browser warns about the certificate (TRAEFIK DEFAULT CERT) and the page is a bare 404 page not found.
3A mapping from the name to the siteA site_domain record in the platform's root orgConnect (and Repair)The site's own not-found screen for a different site, or a 404 for every path.
4The site's domain field, data.domainThe site recordConnectThe site opens, but emails, canonical tags and the sitemap still use the platform address.

Connect (Site Config › Address) does 2, 3 and 4 in one call. 1 is always the owner's job. This page connects, checks, changes, removes and repairs. For what a URL path shows once the domain works, see How a site's addresses route. The site record's fields are in Sites.

How a request finds its site

Tested on the local engine and on live sites, read-only.

  1. DNS sends example.com to the platform's edge (today 38.107.233.133; every *.site.appmint.app name resolves there too).
  2. The edge (Traefik) looks for an ingress whose host is example.com or www.example.com. None: it answers with its own default certificate and a plain 404 page not found. The request never reaches a site. (appengine resources/deploy-template/domain-ingress.tp; tested on live with an unmapped name.)
  3. The site renderer asks the engine for the site: GET /site/get-site/<renderer's site>/<Host header> (base-app src/lib/appmint-client.ts 286–289, called from lib/get-page-data.ts 149).
  4. The engine strips a leading www. and looks up a site_domain record whose domainName equals the host (appengine src/site/site.service.ts 467–480, src/repositories/repository.org.service.ts 280–289). Upper case still matches. The port is part of the host, so a local mapping looks like phasite-phazzer.localhost:3200.
  5. No mapping: the engine falls back to the renderer's own site name, then to its org's Default site (site.service.ts 102–117). On live, an unmapped platform address shows the renderer org's not-found screen ("Welcome to Wemeant") with a 404. Tested.

So www.example.com and example.com open the same site from one mapping. Tested: GET /site/get-site-by-hostname/www.phasite-phazzer.localhost:3200 returns phasite, and the rendered home page is identical on both names.

WhatUsesSource
A link the engine builds while serving a visitor (the site's /api calls)The host the visitor is on. The site's /api proxy sends it as x-client-host, from the browser's Origin or Referer.base-app app/api/[...slug]/route.ts 82–92, lib/appmint-client.ts 100; site.service.ts 1210–1216
A link in an email or a job with no visitordata.domain, else data.hostName, else <site>-<org>.site.appmint.app. https, except localhost hosts.site.service.ts 1210–1216, 1518–1520, 1526–1560
The page's canonical tag and Open Graph URLThe host asked for, except www.<domain> and the site's hostName, which point at https://<domain>. Any other name, aliases included, is its own canonical.base-app src/lib/site-url.ts 51–63; app/[[...slug]]/page.tsx 230, 332
The sitemapdata.domain, else a mapped domain that isn't a platform address, else the host asked forsite.service.ts 1626–1636, 1681–1688
Studio's "View site"data.domain, else data.hostName, else the conventionwebsitemint modern-site-app.tsx 413

Tested on live: https://reesite-reelmoment.site.appmint.app/ and https://www.reelmoment.io/ both carry <link rel="canonical" href="https://reelmoment.io"/>. Neither redirects; they serve the same page.

Ask first

  • Which site? By its name (data.name, e.g. phasite), not its id. The platform address is built from the name you send.
  • The exact domain, without https://, without a path, and without www.: example.com, or a subdomain like shop.example.com. www. is served automatically.
  • Does the person already own it? If not, do they want to buy one here (step 2)? That costs money.
  • Who runs its DNS? Their registrar, Cloudflare, or Home › Domains for a domain bought here. Can they log in there today?
  • Is the domain serving something now (an old website, a shop, email)? Moving its address records takes that old site offline. Don't touch MX or TXT records: email lives there.
  • Is another Appmint site already on it? One domain belongs to one site.

Steps

1. Site Config: what's there now

Read before changing anything.

  • The site: call_appengine GET /site/get-site/<site name>. Note data.domain, data.hostName, data.aliases, data.isDefault, and domainRecord (the site's mappings, attached by getSite, site.service.ts 119–124).
  • Its mappings: call_appengine GET /site/get-site-host-mappings/<site name>. Each row is a site_domain: domainName, orgId, siteId, siteName, status. A healthy site with a domain has two: the platform address and the domain.
  • The address links use: GET /site/base-url?siteName=<site name> → { "url": "https://example.com" }.
  • Who owns a host: GET /site/get-site-by-hostname/<host> returns the site that answers it, or an empty body.
  • DNS today, from any shell: dig +short example.com A, dig +short www.example.com, dig +short NS example.com.

repo_find on site_domain finds nothing: the mappings live in the platform's root org, not the business's. Use the site/… routes above.

Tested on local (phazzer): get-site/phasite shows domain: null, hostName: "phasite-phazzer.localhost:3200", isDefault: true, one mapping; base-url answers http://phasite-phazzer.localhost:3200.

Hands on: the site's name, its current domain and hostName, and its mappings. Check: Site Config › Address shows the same domain (with Edit Domain, Show DNS Settings, Remove Domain) and the Platform host.

2. Domains: buy a domain (only if they don't own one)

Home › Domains (/domains, admin users only, websitemint ui/sidebars/links.ts 21) lists the org's domains and buys new ones. Site Config's Buy Domain opens the same app on its Buy a domain drawer (site-domain-connect.tsx 131–141, components/domain/app.tsx 12–17).

Stop: buying charges the org's card and registers a domain for a year in the owner's name. Show the domain, the price and the registrant details, and wait for yes. Don't buy through the API yourself: the payment is taken by the screen, not by POST /tools/domain/buy, which registers the domain at once (appengine src/tools/tools.domain.service.ts domainBuy), and call_appengine doesn't ask for confirmation on it.

The person's path:

  1. Buy Domain › search a name. Search (GET /tools/domain/search/<name>?suggest=true&provider=cloudflare, public) checks .com .net .org .io .co when no ending is given. The suggestions are always shown as Taken, and a name with no price from the registrar shows $0.00.
  2. Registration Info: first and last name, email, phone, address line 1, city, state, postal code and country (one of US, CA, GB, AU, DE, FR, IN) are required; organization and address line 2 are optional. It is filled from the org's locations and saved back as the business address.
  3. Review & Pay: tick the registration terms, Proceed to Payment (Stripe), then the domain is registered.

What it really does (modern-domain-purchase.tsx 170–261, org-management.service.ts 470–573, tools.domain.service.ts 176–249):

  • The card payment is recorded as a credit top-up to the org, not as a domain purchase. The registrar fee is paid from the platform's registrar account.
  • The Cloudflare registrar path sends only the user's name and the org's system email as the registrant; the address and phone typed in step 2 aren't sent.
  • On success it writes a domain_registration record: domain, registrar, status: "active", purchasedThroughUs: true, autoRenew: true, expiresAt (a year), nameservers, orderId. No monthlyPrice, so renewal billing never picks it up.
  • It creates no DNS records and connects no site. Do step 3, then add the records of step 4.

Other parts of the app, and what they do:

WhereWhat it does
Add Domain ("Add External Domain")Saves a domain_registration with registrar: "external", status: "pending". Nothing else: no mapping, route or check. The window that follows lists email records only (SPF, DKIM, DMARC, with a Google SPF), not website records (domain-add-modal.tsx 47–54, dns-instructions-modal.tsx 64–96).
A domain › DNS Setup › Connect to siteThe same POST /site/update-domain as step 3, for the site picked (domain-details.tsx 124–142). It also ignores deployError.
A domain › DNS Setup › Test configurationReads the domain's Cloudflare zone status (GET /tools/domain/cloudflare/zone/<domain>/activation), not its DNS. An outside domain always shows "Not detected yet (no_zone)", and "Confirmed" doesn't mean it resolves (domain-details.tsx 100–110). Use step 5.
A domain › DNS Records (bought domains only)Add, edit and delete records at the registrar: POST /tools/domain/dns/add, /dns/modify, /dns/delete. On the Cloudflare path the screen reads the wrong record fields, so edit and delete fail, add can be sent without the domain, and TTL and priority are never sent. A failure can come back as success. Check every change with dig.
Link to Site (row action)Opens the domain's drawer. It links nothing.

There is no screen for transfers in or out, nameservers or SSL modes; their API routes exist under /tools/domain/cloudflare/* and nothing calls them. A site's certificate comes from the platform's edge, never from these.

Every tools/domain/* route except search needs someone signed in to the business, of any staff role; the site can't call them (tools.controller.ts 18–20). The registrar, DNS and Cloudflare routes act only on a domain this org bought or connected to one of its sites; any other answers 403 This domain is not connected to your organization (tools.domain.service.ts assertDomainAccess). Change DNS only on the person's yes.

Hands on: the domain, now owned, and who runs its DNS (Home › Domains for a domain bought here). Check: it appears in Home › Domains with "Ours".

3. Site Config: connect the domain

Stop: this changes the live site. It creates the domain's route and certificate request on the platform, and from then on the site's emails, canonical tags and sitemap use the new domain, even before DNS points there. If the site already had a domain, the old one stops working at once. Say exactly that, and wait for yes.

In Studio: Site Config › Address › Custom domain › Connect (websitemint components/welcome-screens/site-domain-connect.tsx 25–57). The same box is on the welcome screen. By API, the call Studio makes:

POST /site/update-domain
{
  "siteOrgId": "<org id>",
  "siteName": "phasite",
  "domainName": "example.com"
}

With call_appengine, orgid and the token are added; send only the body.

FieldRule
siteNameRequired (400 site name is required). The site's name. It is also looked up by id, but the platform address is built as <siteName>-<siteOrgId>.site.appmint.app, so an id here gives the site a wrong platform address.
siteOrgIdDefaults to the orgid header. Another org's id is refused with 403 Cannot change a site in another organization, except from the platform's root org (site.service.ts 2133–2137).
domainNameTrimmed and lower-cased. At least two labels, each a-z 0-9 -, not starting or ending with -, at most 63 characters; at most 253 in all; not an IPv4 address. Else 400 Enter a valid domain name without a protocol or path (2138–2145).
hostOrgId, hostDNSAccepted and not used.
/:domain in the pathAccepted and ignored.

What it does, in order (site.service.ts 2132–2210):

  1. Checks the org may use custom domains (validateCustomDomainUsage, plan-validation.service.ts 90–101). Every plan allows them today (usage/pricing-plan.ts; allowCustomDomains: true on Free, Starter, Professional, Business, Enterprise and every BusinessMade plan). What really fails here is an org that is not active (403 Organization <org> is not active) or missing (404).
  2. Finds the site (404 site <name> not found in org <org>).
  3. 409 Domain <name> is already registered to another site when a mapping for that name belongs to any other site, in any org.
  4. If the site had a different domain: deletes that domain's ingress and its mappings. The old domain stops working now.
  5. Makes sure the platform mapping <siteName>-<org>.site.appmint.app exists and writes it to data.hostName.
  6. Saves data.domain, creates the domain's mapping (status: "active"), and deploys the ingress from resources/deploy-template/domain-ingress.tp.

The answer:

{
  "sharedMapping": { "…": "the platform address's site_domain" },
  "shareHost": "phasite-phazzer.site.appmint.app",
  "siteMapping": { "…": "the domain's site_domain" },
  "site": { "…": "the site record" },
  "domainName": "example.com",
  "siteName": "phasite",
  "deploy": { "…": "present when the ingress was created" },
  "deployError": "present when it was not"
}

A failed route still answers 200. If the ingress could not be created, the answer carries deployError and the domain and mapping are saved anyway (site.service.ts 2198–2208). Studio ignores the answer and shows the DNS window as if all went well (site-domain-connect.tsx 36–48). Always read deployError. If it is there, the domain will show the certificate warning and a bare 404 however the DNS is set; run step 6 once, then tell the platform's support.

Enter the bare domain, never www.example.com. The engine stores what it is given, but strips www. when it looks a host up, so a site saved as www.example.com is found under neither www.example.com nor example.com. The route would be made for www.example.com and www.www.example.com. Studio's box doesn't stop it.

Connect is the only step that does 2, 3 and 4 together. Writing data.domain with repo_update or Site Config's generic fields saves the field and nothing else: no mapping, no route, no certificate.

What the ingress is (resources/deploy-template/domain-ingress.tp): name <domain with dots as dashes>-ingress, namespace base-app, Traefik on the web and websecure entry points, with both example.com and www.example.com sent to the site renderer, and the certificate resolver le-http. That resolver is Let's Encrypt's HTTP challenge: Let's Encrypt must reach the name over plain HTTP on port 80 at the platform before it issues. Each name gets its own certificate. Tested on live: reelmoment.io and www.luxesaver.com each carry a Let's Encrypt certificate for that one name. Deploying again deletes the ingress and creates it fresh (k8s-management.service.ts 197–215).

Hands on: shareHost (the platform address, for the DNS record) and whether deployError is empty. Check: GET /site/get-site-host-mappings/<site name> lists both the platform address and the domain. Site Config › Address shows the domain as a link.

4. DNS: point the domain at the platform (the person's part)

Stop: DNS is the owner's, at their DNS host. Give them the records; don't log in for them. For a domain bought in Home › Domains, its DNS can be edited there (step 2), still only on their yes.

Use these records. They are what working live sites use (dig on reelmoment.io, www.reelmoment.io, www.luxesaver.com, all A 38.107.233.133):

ForTypeNameValue
The bare domainA@The address the site's platform host resolves to. Read it: dig +short <hostName>. Today 38.107.233.133.
wwwCNAMEwwwThe site's hostName, e.g. phasite-phazzer.site.appmint.app (or an A record with the same address as above)
A subdomain site, e.g. shop.example.comCNAMEshopThe site's hostName

Then:

  • Delete any other A, AAAA or CNAME record on @ and www (the old host, a parking page). Two A records means half the visitors reach the old one, and Let's Encrypt can fail.
  • Leave MX, TXT (SPF, DKIM, verification) and other names alone.
  • Cloudflare: set these records to DNS only (grey cloud) until the site opens on https. A proxied record hides the platform from Let's Encrypt's HTTP check.

Don't use the IP addresses Studio shows. Site Config's Show DNS Settings window gives A 38.234.23.56 for @ and www (dns-instructions-popup.tsx 4, 69, 87). Home › Domains › DNS Setup gives the same 38.234.23.56 (domain-details.tsx 17), and its CNAME falls back to <site>.site.appmint.app without the org when the site has no hostName (638). Sites & Dev Environments' window gives A 104.21.94.214 (modern-site-app.tsx 1612). Neither address answers for sites; tested with curl --resolve against both. The CNAME these windows show (the site's hostName) is right. The Site Config window's CNAME option covers only www, so the bare domain needs the A record above.

Right after the first Connect, Site Config's DNS window opens empty. It is handed the site as it was before the connect, when it had no domain, and it draws nothing without one (site-domain-connect.tsx 42–47, dns-instructions-popup.tsx 12). Close it and use Show DNS Settings, or this table. After an Edit Domain it shows the old domain's name.

How long:

  • DNS: as long as the old record's TTL, then the time the DNS host takes to publish. Minutes to an hour is usual; Studio says "15 minutes to 48 hours" (dns-instructions-popup.tsx 99). A brand-new domain can take a few hours to resolve at all.
  • Certificate: Let's Encrypt can only issue once DNS points at the platform. Usually within a few minutes after that. If DNS was wrong when the route was made and the browser still warns an hour after DNS is right, re-make the route once with step 6. Let's Encrypt blocks a name after 5 failed checks in an hour, so don't repeat it.

Hands on: the records the person added, and when. Check: step 5.

5. Check it, live

From any shell (read-only):

dig +short example.com A            # the platform's address, e.g. 38.107.233.133, and nothing else
dig +short www.example.com          # the hostName then the address, or the address
dig +short phasite-phazzer.site.appmint.app   # the address to compare against
curl -sI https://example.com/ | head -1          # HTTP/2 200 (or 404 if the home page is missing — see site routing)
curl -s https://www.example.com/ | grep -o '<link rel="canonical"[^>]*>'   # href="https://example.com"
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -issuer -subject   # issuer: Let's Encrypt; subject: CN = example.com

Read the result:

You seeIt means
dig shows another address, or twoDNS isn't pointed yet, or the old record is still there (step 4)
subject=CN = TRAEFIK DEFAULT CERT and 404 page not foundDNS is right but the platform has no route for this name: the connect didn't make one (deployError), it was removed, or the name was entered with www.
A Let's Encrypt certificate, and the page is "Welcome to …" another site, 404The route is there but no mapping: step 6
200 and the site's own pageDone

Type https://. On live, every plain http:// request to a site — custom domain or platform address — answers 301/308 to https://<host>:8443/, and port 8443 doesn't answer. Tested on reelmoment.io, www.luxesaver.com and a .site.appmint.app address. Browsers that try https first hide this; a printed or typed http:// link fails. Put https:// on every link and QR code.

Hands on: the working https:// address. Check: open it in a browser. Studio's Site Config › Address shows the domain as a link that opens it.

6. Site Config: Repair Site Domain

Studio: Site Config › Address › Repair Site Domain ("Point this site's domain at BusinessMade again. Use it if the site stops opening.", site-action-buttons.tsx 156). Older docs and the API call it fix domains.

POST /site/fix-domains
{ "siteName": "phasite" }

siteOrgId defaults to the orgid header; another org's is refused with 403 Cannot change a site in another organization. It is staff only, with the ConfigAdmin or ContentAdmin role and the content.update permission (site.controller.ts fix-domains).

Stop: it briefly takes every name the site answers on offline while it remakes them. Ask first.

What it does (site.service.ts 1942–2034):

  1. Finds the site.
  2. Deletes every mapping the site has: the platform address, the domain, and any extra name attached by a platform operator.
  3. Re-creates the platform mapping <siteName>-<org>.<SITE_HOST_NAME> (site.appmint.app on live) and writes it to data.hostName.
  4. If data.domain is set, re-creates its mapping and deploys its ingress again (through registerSiteDomainName, 859–902). This is how it fixes a missing route or a stuck certificate.
  5. Answers { success, message, site, removedDomains, addedDomains, cloudflareResults, errors }.

What it doesn't do:

  • Change DNS. The Cloudflare step is commented out (2002–2005); cloudflareResults is always empty. Its API description still says it syncs with Cloudflare.
  • Report the route. An ingress that fails to deploy is logged only; the answer still says success: true and lists the domain in addedDomains.
  • Keep extra names. Anything not the platform address or data.domain is gone after it. Aliases were never mapped, so they lose nothing.
  • Touch data.domain. A domain removed in Sites & Dev Environments (which leaves data.domain) comes back, route and all.

Send the exact site name. Repair looks the site up by its exact name; a name no site has answers 404 Site <name> not found in organization <org> and changes nothing (site.service.ts fixSiteDomains, findSiteByName).

When to use it: the domain shows the certificate warning or the wrong site while DNS is right; the platform address stopped opening; data.hostName is empty (Studio's DNS window then shows your-site.websitemint.com). Not for DNS problems, a 409, or "it still shows my old host".

Hands on: addedDomains and errors. Check: step 5 again, and get-site-host-mappings shows the platform address and the domain.

7. Site Config: change or remove the domain

Stop for either: the old domain stops working the moment the call returns.

Change: Site Config › Address › Edit Domain › type the new one › Connect is the same POST /site/update-domain with the new domainName. It deletes the old domain's route and mappings, then does all of step 3 for the new one. Then the owner points the new domain's DNS (step 4). Studio does nothing if the new name equals the current one (site-domain-connect.tsx 32).

Remove: Site Config › Address › Remove Domain:

POST /site/remove-domain
{ "siteOrgId": "<org id>", "siteId": "<site sk>" }

It deletes the domain's ingress and mapping, and sets data.domain to "" (site.service.ts removeSiteDomain). The platform address and hostName stay, so the site still opens there, and links fall back to it. { "domainName": "example.com" } instead of siteId works only for a domain connected to one of this org's sites (else 404 Domain … is not connected to a site in this organization); a domain another org also holds is refused with 403 Domain … belongs to another organization. It is staff only (ConfigAdmin or ContentAdmin). Answer: { message, domain, results: { ingress, database, site }, site }.

call_appengine treats /site/remove-domain as a delete: the first call comes back asking for the person's confirmation; repeat it with "confirmed": true after their yes (ai/agents/base-agent.ts 914–935).

Tell the person to remove or repoint the DNS records afterwards. Left pointing at the platform, the bare name just shows the certificate warning.

Remove from Site Config, not from Sites & Dev Environments. That app's Remove Domain calls DELETE /site/unregister-site-domain/<site sk>/<domain> (modern-site-app.tsx 298–316 → site.controller.ts 274–278 → site.service.ts 905–914). It deletes the mapping only. The route stays, data.domain stays, so the card still shows the domain, emails and the sitemap still use it, and the next Repair Site Domain puts it back. Site Config's Remove Domain also clears the box even when the call failed (site-domain-connect.tsx 83–97): reload Site Config to see the truth.

Hands on: the site's new data.domain (or none) and its mappings. Check: get-site-host-mappings and get-site/<site name>; for a change, step 5 on the new domain.

Aliases

Site Config › Address › Aliases ("Other hostnames that should serve this site", site-config-form.tsx 799) saves a list to data.aliases. Nothing routes with it. No code registers a mapping or route for an alias, and the renderer doesn't read it; the only reader is analytics, which counts visits on those names as the site's (appengine src/analytics/analytics-site.ts 44).

What actually serves another name:

The other nameHow
www. + the site's domainAlready served by the domain's route and mapping. Just the DNS record (step 4).
A subdomain or another domain instead of the current oneChange the domain (step 7).
A second domain as well, e.g. example.co.uk beside example.comNot possible from Studio for the owner. Only a platform operator can attach a second name (Configuration › System Operations, POST /site/attach-domain, which maps it and makes its route but doesn't set data.domain), and the site's next Repair deletes it. Otherwise, forward it to the main domain at the owner's registrar (a URL redirect), which needs nothing here.

Troubleshooting

SymptomCauseFix
dig shows no address, another address, or twoDNS not pointed, not yet published, or the old record is still thereStep 4. Wait out the old TTL. Nothing on the platform side helps.
Browser: "not secure" / certificate for TRAEFIK DEFAULT CERT; page 404 page not foundNo route for the name on the platform: the connect's deployError, the domain removed, or entered with www.Read data.domain (must be the bare name). Then step 6 once. Still the same: platform support (the ingress could not be created).
Certificate warning that names the right domain but is expired or wrong, or a pending certificate an hour after DNS is rightLet's Encrypt couldn't reach the name over HTTP when the route was made (DNS was elsewhere, Cloudflare proxy on, an AAAA record to the old host)Fix DNS (step 4: grey cloud, no stray AAAA), then step 6 once. Don't repeat it more than a few times an hour.
The domain opens "Welcome to …" another business's or the platform's site, 404Route there, no mapping to this siteStep 6.
409 Domain … is already registered to another siteAnother site has a mapping for that name, in this org or any otherFind which with GET /site/get-site-by-hostname/<domain>. If it's this org's other site, remove it there first (step 7). If it's another org's, the owner must prove the domain is theirs to platform support.
403 … plan does not support custom domains / Organization … is not activeThe org's plan or accountAll current plans include custom domains; an inactive org is a billing matter. Not something to work around.
400 Enter a valid domain name…https://, a path, a space, an IP, or a bad labelSend only example.com.
An alias doesn't openAliases are never mapped or routedSee Aliases.
Still the old site after connectingDNS still points at the old host (dig); the browser's cache; or only one of @ and www was changeddig both names. Open in a private window. Change both records.
Still the old site after moving the domain between two Appmint orgsThe engine remembers which org a host belongs to, with no expiry, and forgets only the exact name that was removed, never its www. form (repository.org.service.ts 250–273)Pages come from the right site; forms, cart and sign-in calls on www. can still go to the old org. Platform support must clear the cache entry www.<domain>-org-id.
Emails and Studio still link the platform addressdata.domain empty: the domain was attached some other way, or removedConnect it in Site Config (step 3).
Emails link the removed domainRemoved in Sites & Dev Environments, which leaves data.domainRemove it in Site Config (step 7).
http://example.com doesn't openThe platform's HTTP redirect goes to port 8443Use https:// (step 5 warning).

When it's done

Tell the person, in a few lines:

  • the domain and the site it opens, and the https:// address that now works (or what's still pending: DNS, certificate);
  • the DNS records they added, and any old ones they should delete;
  • that www. works too, and that links, emails, the canonical tag and the sitemap now use the domain;
  • for a change or removal, what the old domain does now.

Open Site Config › Address and the domain in a browser.

What it can't do yet

  • No status for the route or certificate. Nothing in Studio or the API says whether the ingress exists or the certificate issued. site_domain.status is active the moment it's created. Check with curl and openssl (step 5).
  • No DNS check. Connect doesn't look at DNS; it succeeds for a domain nobody pointed.
  • Studio's DNS windows give wrong IP addresses (step 4) and the first one opens empty.
  • Plain http:// is broken on live (redirect to port 8443).
  • One domain per site. Aliases do nothing; a second domain is operator-only and lost on Repair.
  • Repair doesn't sync Cloudflare and can't report a failed route.
  • Dev environments: Sites & Dev Environments › Attach Domain on a dev environment only adds the name to its domains list. The route is commented out (dev-environment.service.ts 399–413) and removal says "not yet implemented".
  • Connect may leave hostName as it was. It writes data.hostName, then saves the whole site from a copy read before that write (site.service.ts 2151, 2181, 2195). A site with no hostName keeps none; Repair sets it.

Don'ts

  • Don't call Connect, Repair or Remove without the person's yes. Each changes what the live site answers on at once.
  • Don't write data.domain with repo_update. It routes nothing.
  • Don't enter www., https:// or a path.
  • Don't give the IP addresses from Studio's DNS windows. Read the platform host's address with dig.
  • Don't touch the owner's MX, TXT or other records, and don't log in to their DNS host for them.
  • Don't use Sites & Dev Environments' Remove Domain for a site.
  • Don't run Repair over and over. A few times an hour at most, and only once DNS is right.
  • Don't promise an alias will open the site.
  • Don't call site/fix-domains with a site name you haven't read back from get-site.