How to Point a Domain at Your App From the Terminal
Pointing a domain at an app comes down to a handful of DNS records and a way to confirm that the rest of the internet sees them. You can do all of it from a shell: read the current records with dig, change them through your DNS provider's API, and check the result against a public resolver before you call the job done.
The records that matter
Most app deployments touch four record types. Everything else in a zone (MX for mail, CAA for certificate authorities, NS for delegation) usually stays as it is.
| Record | Holds | Typical use |
|---|---|---|
| A | An IPv4 address | Your server or load balancer has a fixed IPv4 address |
| AAAA | An IPv6 address | The same, for IPv6 clients |
| CNAME | Another hostname | Your platform gives you a hostname instead of an IP |
| TXT | Free-form text | Ownership checks, mail policy, certificate challenges |
A CNAME tells resolvers "this name is an alias, go look up that other name instead." Many hosting platforms ask for one because it lets them change their own IP addresses without asking you to edit anything.
TXT records are how services confirm you control a domain. A platform hands you a token, you publish it as a TXT record, and it looks the record up. Certificate authorities can use the same mechanism: Let's Encrypt's DNS-01 challenge asks you to put a specific value in a TXT record at _acme-challenge.<your domain>.
Why the apex cannot hold a plain CNAME
The apex is the bare domain, example.com, as opposed to www.example.com or app.example.com. The original DNS specification, RFC 1034, is explicit: "If a CNAME RR is present at a node, no other data should be present". The apex always carries SOA and NS records, so a CNAME there breaks the rule.
You have two ways around it. Use A and AAAA records at the apex if your host gives you stable addresses. Or use a provider feature that accepts a CNAME-style target at the apex and answers with addresses. Cloudflare calls this CNAME flattening, and its docs say it happens by default for all plans when the apex uses a CNAME. Other DNS hosts offer similar features under their own names, so check yours. Subdomains such as www or app can hold an ordinary CNAME.
Read what is there now with dig
Before changing anything, look at what the world currently sees. These forms cover almost every check:
# Just the answer section, with TTLs
dig +noall +answer app.example.com A
dig +noall +answer app.example.com AAAA
# Terse output, handy in scripts
dig +short TXT example.com
# Ask a specific public resolver
dig +noall +answer app.example.com A @1.1.1.1
# Ask the zone's own nameserver, skipping every cache
NS=$(dig +short NS example.com | head -1)
dig +noall +answer app.example.com A @"$NS"
# Walk the delegation from the root down
dig +trace app.example.com
An answer line reads left to right as name, TTL in seconds, class, type and value:
app.example.com. 300 IN A 203.0.113.10
Compare the TTL from a public resolver with the TTL from the authoritative nameserver. The authoritative server shows the value you configured. A recursive resolver usually shows what is left of its cached copy, so the number counts down between queries. That remaining TTL tells you how long until that resolver asks again.
TTL and what propagation actually means
DNS does not push changes out. Resolvers cache answers and ask again when their copy expires. RFC 1034 defines the TTL as "a time limit on how long an RR can be kept in a cache." So the "propagation" delay after an edit is simply the old TTL running out in caches around the world.
That gives you a simple routine for planned moves:
- Lower the TTL on the record you plan to change, for example to 300 seconds.
- Wait at least one full period of the old TTL, so every cache has picked up the short one.
- Make the change. Caches now refresh within about five minutes.
- Once the new value is confirmed, raise the TTL again to cut query load.
Two things sit outside this routine. First, a nameserver change at your registrar updates delegation records held in the parent zone, under the registry's TTLs, which you do not control, so it settles more slowly than a record edit.
Second, "this name does not exist" answers are cached too. Under RFC 2308, a negative answer's TTL comes from "the minimum of the SOA.MINIMUM field and SOA's TTL." If you query app.example.com before creating it, a resolver can keep telling you it does not exist for that long. In the SOA answer, the second field is the SOA's own TTL and the last field is MINIMUM; the smaller of the two is your negative-caching time:
dig +noall +answer example.com SOA @"$NS"
Worked example: update an A record with the Cloudflare API
Say app.example.com points at an old server and you want it on 203.0.113.10. You need a Cloudflare API token that can edit DNS for this zone and nothing else, stored in the CLOUDFLARE_API_TOKEN environment variable, plus the zone ID, which appears in the API section of the zone's Overview page in the dashboard.
First, find the record's ID. The list endpoint accepts a type filter and an exact name filter:
ZONE_ID="your-zone-id"
RECORD_ID=$(curl -s \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=A&name.exact=app.example.com" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq -r '.result[0].id')
echo "$RECORD_ID"
If that prints null, the record does not exist yet, and you would create it with a POST to the same dns_records path instead.
Then send the update. Cloudflare's edit endpoint is a PATCH to the record's URL:
curl -s -X PATCH \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "A",
"name": "app.example.com",
"content": "203.0.113.10",
"ttl": 300,
"proxied": false
}' | jq '{success, errors, content: .result.content}'
A few details from the API reference, as of September 2026, are worth knowing. The name field is the complete record name including the zone, not a short label like app. The ttl is in seconds and must fall between 60 and 86400 (Enterprise zones can go down to 30), and a value of 1 means automatic. Always read success and errors in the response rather than assuming a quiet command worked.
The proxied flag changes what you will see afterward. When a record is proxied, Cloudflare's docs say DNS queries are answered with Cloudflare anycast IP addresses instead of your origin, and the TTL is Auto, which is 300 seconds and cannot be edited. If you set proxied to true and then dig returns an address you do not recognize, that is expected.
Verify against a public resolver
Now check from the outside, not from your laptop's cache:
dig +short app.example.com A @1.1.1.1
dig +short app.example.com A @8.8.8.8
dig +short app.example.com A @"$NS"
The authoritative answer should show the new address within moments. Public resolvers show it once their cached copy expires. If one still returns the old value, compare its TTL with the old TTL before you assume something is broken.
You can also test the new server before DNS catches up. curl's --resolve option pins a hostname to an address for a single request, so TLS and virtual hosting behave exactly as they will for real visitors:
curl -sI --resolve app.example.com:443:203.0.113.10 https://app.example.com/
Run that before you touch DNS and you will know the new server answers correctly, which turns the DNS change into the smallest step of the move.
Common mistakes
- Testing only from your own machine. Your operating system and browser keep their own caches, and an
/etc/hostsentry overrides DNS entirely. Query a public resolver and the authoritative server directly. - Forgetting the AAAA record. If the old host still has an IPv6 address published, clients that prefer IPv6 keep reaching it. Update or delete AAAA alongside A.
- A CNAME next to other records. Cloudflare's API notes that A and AAAA records cannot exist on the same name as a CNAME. Remove the conflict first.
- Querying a name before it exists. Your resolver caches the "does not exist" answer. Create the record, then look it up.
- Replacing instead of adding a TXT value. A name can hold several TXT records. A verification token belongs next to your existing mail policy record, not in place of it.
- Leaving the short TTL in place. It works, but it multiplies lookups. Raise it again once the change has settled.
The habit that prevents most of these is simple: after every change, ask the authoritative nameserver first, then two public resolvers, and only then open a browser.