NXDOMAIN — Subdomain Typo or Missing DNS Record
الأعراض
- `dig api.example.com` returns `status: NXDOMAIN` while `dig example.com` works
- `curl https://api.example.com` fails with `Could not resolve host: api.example.com`
- Browser shows `DNS_PROBE_FINISHED_NXDOMAIN` for the subdomain only
- Application log: `Name or service not known` when connecting to a newly deployed service
- `nslookup staging.example.com` returns `** server can't find staging.example.com: NXDOMAIN`
- `curl https://api.example.com` fails with `Could not resolve host: api.example.com`
- Browser shows `DNS_PROBE_FINISHED_NXDOMAIN` for the subdomain only
- Application log: `Name or service not known` when connecting to a newly deployed service
- `nslookup staging.example.com` returns `** server can't find staging.example.com: NXDOMAIN`
الأسباب الجذرية
- DNS A or CNAME record for the subdomain was never created in the DNS zone
- Typo in the subdomain name in the DNS provider dashboard (e.g., 'apu' instead of 'api')
- DNS propagation delay — the record was just created and the TTL has not yet expired globally
- Broken CNAME chain — subdomain's CNAME target itself has no A record (dangling CNAME)
- Wildcard record (*.example.com) not configured and no explicit record exists for the subdomain
التشخيص
**Step 1 — Confirm the record does not exist**
```bash
# Query authoritative NS directly to bypass resolver cache
NS=$(dig example.com NS +short | head -1)
dig @$NS api.example.com A
# NXDOMAIN from authoritative = record truly missing
# Also check CNAME
dig @$NS api.example.com CNAME
```
**Step 2 — Check the DNS provider dashboard for typos**
Log in to your DNS provider (Cloudflare, Route 53, GoDaddy) and list all records for the zone. Look for:
- Misspelled subdomain names (e.g., `apu` vs `api`)
- Records with a trailing dot missing in the value
- CNAME pointing to an incorrect or non-existent target
**Step 3 — Check CNAME chain integrity**
```bash
# Follow the chain manually:
dig api.example.com CNAME +short
# e.g., returns: myapp.heroku.com.
dig myapp.heroku.com A +short
# Empty = dangling CNAME; the target itself has no A record
```
**Step 4 — Check DNS propagation if the record was just created**
```bash
# Query multiple public resolvers to see propagation status:
dig @8.8.8.8 api.example.com A # Google — US
dig @1.1.1.1 api.example.com A # Cloudflare — global
dig @9.9.9.9 api.example.com A # Quad9
# Check propagation ETA from record TTL:
dig example.com SOA +short
# Last field = negative TTL — maximum cache time for NXDOMAIN
```
**Step 5 — Flush local DNS cache**
```bash
# macOS:
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder
# Linux (systemd-resolved):
sudo systemd-resolve --flush-caches
# Windows:
ipconfig /flushdns
```
```bash
# Query authoritative NS directly to bypass resolver cache
NS=$(dig example.com NS +short | head -1)
dig @$NS api.example.com A
# NXDOMAIN from authoritative = record truly missing
# Also check CNAME
dig @$NS api.example.com CNAME
```
**Step 2 — Check the DNS provider dashboard for typos**
Log in to your DNS provider (Cloudflare, Route 53, GoDaddy) and list all records for the zone. Look for:
- Misspelled subdomain names (e.g., `apu` vs `api`)
- Records with a trailing dot missing in the value
- CNAME pointing to an incorrect or non-existent target
**Step 3 — Check CNAME chain integrity**
```bash
# Follow the chain manually:
dig api.example.com CNAME +short
# e.g., returns: myapp.heroku.com.
dig myapp.heroku.com A +short
# Empty = dangling CNAME; the target itself has no A record
```
**Step 4 — Check DNS propagation if the record was just created**
```bash
# Query multiple public resolvers to see propagation status:
dig @8.8.8.8 api.example.com A # Google — US
dig @1.1.1.1 api.example.com A # Cloudflare — global
dig @9.9.9.9 api.example.com A # Quad9
# Check propagation ETA from record TTL:
dig example.com SOA +short
# Last field = negative TTL — maximum cache time for NXDOMAIN
```
**Step 5 — Flush local DNS cache**
```bash
# macOS:
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder
# Linux (systemd-resolved):
sudo systemd-resolve --flush-caches
# Windows:
ipconfig /flushdns
```
الحل
**Fix 1 — Create the missing DNS record**
```bash
# Cloudflare API — create A record for subdomain:
CF_TOKEN='your_api_token'
ZONE_ID='your_zone_id'
curl -X POST \
'https://api.cloudflare.com/client/v4/zones/'"$ZONE_ID"'/dns_records' \
-H "Authorization: Bearer $CF_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"type":"A","name":"api.example.com","content":"203.0.113.1","ttl":300,"proxied":false}'
# Or a CNAME:
-d '{"type":"CNAME","name":"api","content":"myapp.example.com","ttl":300}'
```
**Fix 2 — Fix a typo in an existing record**
```bash
# Find the wrong record ID:
curl -s -H 'Authorization: Bearer '$CF_TOKEN \
'https://api.cloudflare.com/client/v4/zones/'$ZONE_ID'/dns_records?name=apu.example.com' \
| jq '.result[0].id'
# Delete the misspelled record and create the correct one:
RECORD_ID='abc123'
curl -X DELETE -H 'Authorization: Bearer '$CF_TOKEN \
'https://api.cloudflare.com/client/v4/zones/'$ZONE_ID'/dns_records/'$RECORD_ID
```
**Fix 3 — Fix a dangling CNAME**
Update the CNAME target to a hostname that has an A record, or change the CNAME to point directly to an IP with an A record:
```bash
# Verify the target resolves before pointing your CNAME to it:
dig myapp.example.com A +short
# Must return an IP, not an empty response
```
**Fix 4 — Add a wildcard record to catch future subdomains**
```dns
; Wildcard A record — catches any subdomain without a specific record
*.example.com. 300 IN A 203.0.113.1
```
```bash
# Cloudflare API — create A record for subdomain:
CF_TOKEN='your_api_token'
ZONE_ID='your_zone_id'
curl -X POST \
'https://api.cloudflare.com/client/v4/zones/'"$ZONE_ID"'/dns_records' \
-H "Authorization: Bearer $CF_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"type":"A","name":"api.example.com","content":"203.0.113.1","ttl":300,"proxied":false}'
# Or a CNAME:
-d '{"type":"CNAME","name":"api","content":"myapp.example.com","ttl":300}'
```
**Fix 2 — Fix a typo in an existing record**
```bash
# Find the wrong record ID:
curl -s -H 'Authorization: Bearer '$CF_TOKEN \
'https://api.cloudflare.com/client/v4/zones/'$ZONE_ID'/dns_records?name=apu.example.com' \
| jq '.result[0].id'
# Delete the misspelled record and create the correct one:
RECORD_ID='abc123'
curl -X DELETE -H 'Authorization: Bearer '$CF_TOKEN \
'https://api.cloudflare.com/client/v4/zones/'$ZONE_ID'/dns_records/'$RECORD_ID
```
**Fix 3 — Fix a dangling CNAME**
Update the CNAME target to a hostname that has an A record, or change the CNAME to point directly to an IP with an A record:
```bash
# Verify the target resolves before pointing your CNAME to it:
dig myapp.example.com A +short
# Must return an IP, not an empty response
```
**Fix 4 — Add a wildcard record to catch future subdomains**
```dns
; Wildcard A record — catches any subdomain without a specific record
*.example.com. 300 IN A 203.0.113.1
```
الوقاية
- Use Infrastructure as Code (Terraform, Pulumi) to manage DNS records and catch typos in code review
- Before deploying a new service, create its DNS record with a low TTL (60s) and verify resolution
- Keep the SOA minimum TTL low (300s) to reduce the time NXDOMAIN is cached after a fix
- Validate CNAME targets exist before publishing the CNAME record in production
- Add automated DNS health checks for all subdomains used by your application
- Before deploying a new service, create its DNS record with a low TTL (60s) and verify resolution
- Keep the SOA minimum TTL low (300s) to reduce the time NXDOMAIN is cached after a fix
- Validate CNAME targets exist before publishing the CNAME record in production
- Add automated DNS health checks for all subdomains used by your application