Skip to main content

What is Domain Connect?

Domain Connect is an open protocol that lets service providers (us) configure DNS records on a user’s domain with one click, instead of asking them to copy-paste CNAME and TXT values manually. The user gets redirected to their DNS provider (e.g. Cloudflare), approves the changes, and the records are created automatically.

How it works in our stack

User adds custom domain in dashboard

ctrl API: AddCustomDomain
  1. Generates CNAME target + verification token
  2. Looks up domain's nameservers
  3. Checks if the DNS provider supports Domain Connect
     (via _domainconnect.{provider} TXT lookup)
  4. If yes: builds a signed redirect URL using our private key
  5. Stores domain + DC provider/URL in custom_domains table

Dashboard shows "Automatic setup available" card
  → User clicks "Connect"
  → Redirected to DNS provider's consent page
  → Provider creates CNAME + TXT records
  → Redirects back to app.unkey.com/{workspace}/projects/{project}/settings

Existing verification worker picks up the records (polls every 1 min)
  → ACME certificate issued
  → Frontline route created
  → Domain is live

Components

Template

The Domain Connect template defines what DNS records we need. It lives in the public Domain-Connect/templates repo as unkey.com.custom-domain.json.
SettingValueWhy
providerIdunkey.comOur provider identifier
serviceIdcustom-domainService identifier
hostRequiredtrueSubdomains only (apex uses manual setup)
syncBlockfalseSynchronous flow (required by Cloudflare)
syncPubKeyDomaindomainconnect.unkey.comWhere providers fetch our public key
syncRedirectDomainapp.unkey.comWhere providers redirect after approval
Records created:
TypeHostValue
CNAME@%target% (full CNAME target, e.g. abc123.unkey-dns.com)
TXT_unkeyunkey-domain-verify=%verificationToken%
To update the template, open a PR against Domain-Connect/templates. Use the dc-template-linter to validate: dc-template-linter -cloudflare unkey.com.custom-domain.json.

Signing keypair

Domain Connect requires all requests to be digitally signed (RS256). We have an RSA keypair:
  • Public key: published as DNS TXT records at _dcpubkeyv1.domainconnect.unkey.com, split into two parts (p=1 and p=2) due to TXT record size limits
  • Private key: stored in AWS Secrets Manager under unkey/control as UNKEY_DOMAIN_CONNECT_PRIVATE_KEY (PEM format)
The DNS records are managed via Pulumi in infra/pulumi/projects/dns/unkey-com/main.go.

Discovery library

We use railwayapp/domainconnect-go which handles:
  • DNS provider discovery (NS lookup → _domainconnect.{provider} TXT check)
  • Sync URL construction with all required parameters
  • RS256 signing with our private key
The wrapper is in pkg/dns/domainconnect/discover.go.

Code

Discovery and signing live in pkg/dns/domainconnect/. The ctrl service calls Discover() during AddCustomDomain and persists the result. If no private key is configured, Domain Connect is silently disabled.

Supported DNS providers

Any provider that publishes a _domainconnect.{provider-domain} TXT record is automatically supported. As of now:
ProviderNotes
CloudflareTemplate onboarded via email to domain-connect@cloudflare.com
Vercel DNSAuto-discovered
DigitalOcean, Name.com, Hostinger, Dynadot, NamesiloUse Cloudflare under the hood
IONOSOwn Domain Connect endpoint
To onboard with a new provider, they need to pull our template from the templates repo. Some providers (like Cloudflare and Vercel) require manual registration via email.

Key rotation

If you need to rotate the signing keypair:
  1. Generate a new keypair:
openssl genrsa -out domain-connect-private.pem 2048
openssl rsa -in domain-connect-private.pem -pubout -outform DER \
  | base64 | tr -d '\n' > domain-connect-pubkey.b64
  1. Update the DNS TXT records at _dcpubkeyv1.domainconnect.unkey.com with the new public key chunks
  2. Update UNKEY_DOMAIN_CONNECT_PRIVATE_KEY in AWS Secrets Manager (unkey/control)
  3. Restart ctrl service to pick up the new key
Existing signed URLs in the database will become invalid. Users will need to re-add their domain to get a new signed URL.

Verifying the setup

Check public key is published

dig TXT _dcpubkeyv1.domainconnect.unkey.com

Verify a signature

Go to exampleservice.domainconnect.org/sig, enter:
  • Key: _dcpubkeyv1
  • Domain: domainconnect.unkey.com
  • Paste the query string and signature from a generated URL

Validate template

go install github.com/Domain-Connect/dc-template-linter@latest
dc-template-linter -cloudflare unkey.com.custom-domain.json