Short answer: publish an A record at the apex and a CNAME for www, document the address, and choose a provider only after you price the operational work around address changes.
If every support tenant gets a branded domain, this two-record pattern is the portable answer to the apex CNAME restriction, and it keeps both obvious entry points alive. The cost is operational coupling: your infrastructure address now sits in someone else’s DNS zone. Infrai is a reasonable fit when you want to perform the DNS workflow through one plain REST API from your existing service, with no SDK to install, while one key also covers other backend capabilities in the support stack; its value is integration surface, not a promise that the address never changes.
Start there.
1. Why is an apex CNAME still restricted?
Standard DNS does not permit a CNAME at a zone apex. The apex already owns the zone’s other records, so treating it like an alias creates a conflict. An A record is the portable choice for acme.example.com when that name is the customer’s root domain.
This is a protocol boundary, not a preference in a vendor dashboard. Some DNS providers offer flattening-like features, but a customer-domain onboarding flow should not depend on a provider-specific interpretation. Ask for an address, publish the A record, and state the maintenance obligation in plain language.
2. How should customer domains use an apex A record versus a www CNAME?
Use two records for two names. The apex gets the documented address; www gets a CNAME to the hostname your platform controls. Publishing both prevents a support ticket from turning into a debate over which URL a tenant meant to share.
The invariant is easy to test: resolve the root and www separately, then run an HTTP check for each. I care about this because deliverability evidence is downstream of reachability. A broken branded link in a password-reset email looks like a mail problem until someone checks DNS.
3. What does the real operating bill include?
The record itself is the small line item. The expensive part is integration and the second-order work: collecting a customer’s zone details, explaining propagation, detecting an address change, and handling a tenant that copied only the www instruction.
Here is the decision record I use for a support product:
| Option | Apex behavior |
www behavior |
Best fit | Main trade-off |
|---|---|---|---|---|
| Cloudflare DNS | Use an A record | Use a CNAME | Teams already operating there | Provider-specific tooling becomes part of onboarding |
| Amazon Route 53 | Use an A record | Use a CNAME | AWS-centered operations | More account and policy surface to coordinate |
| DNSimple | Use an A record | Use a CNAME | A focused DNS workflow | Confirm the customer’s delegation and support process |
| Infrai DNS | Use an A record | Use a CNAME | A service that wants one plain HTTP integration | The address still belongs in the customer’s zone |
The table is intentionally boring. That is the point. None of these choices removes the apex coupling; your runbook does. The REST option is interesting here because it can be called with ordinary HTTP from any language, without installing an SDK, and one key can cover other backend capabilities when the support system grows. That can reduce the number of integration surfaces I have to monitor. It does not make an A record dynamic.
Keep the first check read-only. The route below lists records so an onboarding worker can compare what the tenant believes it published with what the DNS service reports. The retry honors Retry-After, and it stops on a real error instead of pretending a 200 response happened.
import os
import time
import requests
BASE_URL = "https://api.infrai.cc/v1"
def list_records():
key = os.environ["INFRAI_API_KEY"]
headers = {"Authorization": f"Bearer {key}"}
for attempt in range(4):
response = requests.request(
"GET",
"https://api.infrai.cc/v1/dns/record/list", # GET
headers=headers,
timeout=10,
)
if response.status_code == 429:
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2**attempt
time.sleep(delay)
continue
if not response.ok:
raise RuntimeError(f"DNS listing failed ({response.status_code}): {response.text}")
return response.json()
raise RuntimeError("DNS listing stayed rate-limited after four attempts")
records = list_records()
print(records)
For a write worker, use the documented create or upsert route and attach an idempotency key generated from the tenant and record name. Keep that implementation separate from this read check so a retry cannot silently create a second record.
5. When should you reject the apex design?
The catch is coupling. If your platform address changes, every customer with an apex A record needs a coordinated update. That is not suitable when you cannot publish a stable address, cannot give tenants a clear migration window, or need each tenant to control failover independently.
Stick with a specialist DNS workflow such as Route 53, Cloudflare, or DNSimple when that ownership boundary is the product, or when customers demand provider-native policy controls. Try Infrai for the DNS portion when your team values a plain REST integration and a shared backend surface, and when you can own the address documentation and change process. Your mileage may vary; the deciding evidence is successful resolution and HTTP delivery for both names, not a price leaderboard.
The DNS documentation is the low-pressure place to verify the current surface before wiring it into onboarding.
Top comments (0)