Appendix E: Developer FAQ
15 common DNS questions from developers, answered properly. Format: Question → Direct Answer → How it works → The command → Common mistake.
Appendix E: Developer FAQ
The DNS questions that keep appearing on Stack Overflow, answered with enough depth to understand why the answer is what it is.
1. How do I set up a custom domain for my GitHub Pages site?
Direct Answer:
For a subdomain (www.example.com): add a CNAME record pointing to yourusername.github.io. For the apex domain (example.com): use four A records pointing to GitHub's IP addresses, or use an ALIAS/ANAME record if your DNS provider supports it.
How it works:
GitHub Pages identifies which site to serve based on the hostname in the HTTP request. When a request arrives at GitHub's servers for www.example.com, they look for a repository configured for that custom domain. The DNS record routes traffic to GitHub's servers; the HTTP Host header tells GitHub which site to show.
The commands:
# CNAME for www
www.example.com. CNAME yourusername.github.io.
# A records for apex (GitHub's current IPs)
example.com. A 185.199.108.153
example.com. A 185.199.109.153
example.com. A 185.199.110.153
example.com. A 185.199.111.153
Common mistake:
Using a CNAME at the apex domain. You can't have a CNAME at example.com because the DNS spec prohibits CNAME records sharing a node with any other record type — and the apex always has SOA and NS records. If your DNS provider supports ALIAS/ANAME records, use those. Otherwise, use the A records.
2. Why don't cookies work across my subdomains?
Direct Answer:
Cookies are scoped by domain. A cookie set on app.example.com is not sent to api.example.com unless it was set with Domain=.example.com (note the leading dot, which includes all subdomains).
How it works:
The browser enforces the same-origin policy for cookies. The Domain attribute on a cookie determines which requests it's sent with. If you set a cookie with Domain=example.com (or .example.com), it's sent to example.com and all its subdomains. If you don't set a Domain attribute, the cookie is scoped to the exact hostname that set it.
DNS itself doesn't control cookie scoping — this is entirely a browser behavior. But the structure of your DNS (how you use subdomains) determines what's possible.
The command: Set cookies server-side with the correct domain scope:
Set-Cookie: session=abc123; Domain=.example.com; Secure; HttpOnly; SameSite=Lax
Common mistake:
Using Domain=app.example.com when you need the cookie shared across subdomains. The subdomain-scoped cookie won't be sent to api.example.com. Set Domain=.example.com to share across all subdomains.
3. Can I get an SSL certificate for a bare IP address?
Direct Answer:
No, from public CAs. Let's Encrypt and public certificate authorities don't issue certificates for bare IP addresses (there's no certificate for 203.0.113.10). Private CAs can issue certificates for IPs. Most production architectures avoid this by putting a hostname in front of every service.
How it works: TLS certificates identify a server by hostname (Common Name or Subject Alternative Name). CAs validate that you control the hostname before issuing. There's no equivalent validation mechanism for IP addresses in the public CA infrastructure. IP SANs technically exist in the certificate spec but are not supported for public issuance by standard CAs.
The practical solution:
Give your service a hostname. Even if it's internal (api.internal.example.com or using a private DNS zone), having a hostname lets you use a private CA or self-signed certificate with a proper name. For public services, use a domain name.
Common mistake: Trying to configure Nginx or Apache to serve TLS on an IP address without a hostname. The TLS layer needs a hostname to do certificate validation; without it, clients will get a certificate mismatch error.
4. How does DNS relate to URL parsing in my application?
Direct Answer:
DNS resolves the hostname part of a URL. When you parse https://api.example.com:8443/v1/users, the hostname is api.example.com — that's what DNS resolves. The scheme, port, path, and query string are not visible to DNS.
How it works:
URL parsing extracts: scheme (https), hostname (api.example.com), port (8443 or default 443 for HTTPS), path (/v1/users). The DNS lookup uses only the hostname. Everything else is handled at the HTTP layer after the TCP connection is established.
The command:
// Node.js URL parsing
const url = new URL('https://api.example.com:8443/v1/users');
console.log(url.hostname); // 'api.example.com' — what DNS resolves
console.log(url.port); // '8443' — port, not in DNS
console.log(url.pathname); // '/v1/users' — path, not in DNS
Common mistake:
Assuming you can include the port in a DNS record. DNS resolves hostnames to addresses. Port information lives in the application layer (or in SRV records if you're using service discovery). api.example.com:8443 is not a valid DNS lookup target — the :8443 is stripped before the DNS query is sent.
5. What regex should I use to validate DNS names in my application?
Direct Answer: Labels (the parts between dots) must be 1-63 characters, starting and ending with a letter or digit, with only letters, digits, and hyphens in between. Total FQDN length maximum 253 characters.
How it works:
RFC 1123 defines the LDH (Letter-Digit-Hyphen) rule. An internationalized domain name (IDN) like münchen.de is represented in DNS as xn--mnchen-3ya.de (Punycode). The regex below handles ASCII DNS names; IDN validation requires Punycode normalization first.
The pattern:
// Validates a hostname (not including port or scheme)
const hostnameRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
// Per RFC 1123 — label-by-label validation
function isValidHostname(hostname) {
if (hostname.length > 253) return false;
const labels = hostname.split('.');
return labels.every(label =>
label.length >= 1 &&
label.length <= 63 &&
/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(label)
);
}
Common mistake:
Rejecting valid internationalized domain names because the Punycode representation wasn't expected (xn-- prefix). If your application is used internationally, normalize to Punycode before validating, or use a proper IDN library.
6. How does the authoritative lookup hierarchy actually work?
Direct Answer: Start at the root, follow delegations down to the authoritative nameserver, get the answer. The recursive resolver does this for you; you see only the final answer.
How it works:
When you query api.example.com:
- Recursive resolver asks a root nameserver: "Who handles
.com?" - Root says: "Ask these nameservers for
.com" (returns NS records for the.comTLD) - Recursive resolver asks
.comTLD server: "Who handlesexample.com?" - TLD server returns NS records for
example.com(the delegation) - Recursive resolver asks
example.com's authoritative nameserver: "What isapi.example.com?" - Authoritative server returns the A record
The recursive resolver caches each step according to the TTL. On subsequent queries, it skips cached steps.
The command:
# Watch the full resolution chain
dig +trace api.example.com A
Common mistake:
Updating records at the authoritative server and expecting immediate global availability. The delegation chain is cached. If the .com TLD cached the NS delegation with a 172800s TTL, it won't re-fetch the delegation for 48 hours. Changes to NS records take longer to propagate than changes to zone records.
7. What do the DNS error codes actually mean?
Direct Answer:
NOERROR— query succeeded, answer may or may not have recordsNXDOMAIN— the queried name does not existSERVFAIL— server error, couldn't answer the queryREFUSED— server won't answer this query (policy/access control)FORMERR— malformed queryNOTIMP— query type not implemented by this server
How it works:
These are the RCODE values in DNS response messages. NOERROR with an empty answer section means the name exists but has no records of the queried type. NXDOMAIN means the name itself doesn't exist. SERVFAIL can mean the authoritative server is unreachable, DNSSEC validation failed, or an internal server error. REFUSED typically means the resolver won't answer queries from your IP (recursive resolvers restrict this to authorized clients).
The command:
# Check the RCODE in the response
dig example.com A | grep "status:"
# Output: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, ...
Common mistake:
Treating NXDOMAIN and SERVFAIL as equivalent "DNS failures." They're different problems with different solutions. NXDOMAIN means the record genuinely doesn't exist — check your DNS configuration. SERVFAIL means something is wrong with the resolution chain — check whether your authoritative servers are reachable and whether DNSSEC validation is working.
8. How do I set up DNS for local development?
Direct Answer:
For local development, /etc/hosts for simple cases, or lvh.me (which resolves all subdomains to 127.0.0.1) for subdomain testing.
How it works:
/etc/hosts is checked before DNS resolution (on most systems). Adding 127.0.0.1 myapp.local makes myapp.local resolve to localhost without DNS. This doesn't support subdomains or wildcard patterns.
lvh.me is a public DNS service that resolves *.lvh.me to 127.0.0.1. So myapp.lvh.me, app.myapp.lvh.me, and api.myapp.lvh.me all resolve to localhost. You can use it for testing multi-subdomain setups without configuring anything locally.
For more complex local DNS (custom TLDs, split-horizon), use dnsmasq:
# dnsmasq config: resolve *.local to localhost
address=/.local/127.0.0.1
Common mistake:
Using .local as a development TLD on macOS. .local is reserved for mDNS (Bonjour) on macOS, causing resolution conflicts. Use .test, .localhost, or .example instead — these are IANA-reserved for local/testing use.
9. How do I manage DNS records for many subdomains programmatically?
Direct Answer: Use your DNS provider's API or a tool like Terraform/OctoDNS to manage records as code. Most providers have REST APIs; the major ones have official Terraform providers.
How it works: DNS providers expose APIs for record management. Cloudflare, Route 53, NS1 — all have well-documented REST APIs. For bulk operations: export your current zone, modify it programmatically, and import it back, or use a declarative tool (OctoDNS) that manages the full desired state.
The command (Cloudflare API example):
# Create a DNS record via Cloudflare API
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"type":"A","name":"subdomain.example.com","content":"203.0.113.10","ttl":300}'
Common mistake: Managing DNS manually through a console and ending up with records that don't match your infrastructure. When you have dozens or hundreds of subdomains, the console becomes unmanageable. DNS-as-code pays off at scale.
10. My hosting platform says to use a CNAME, but my DNS provider says I can't use a CNAME at the apex. What do I do?
Direct Answer: Use an ALIAS or ANAME record if your DNS provider supports it, or use a DNS provider that supports CNAME flattening (Cloudflare, Route 53 with ALIAS).
How it works: The DNS spec prohibits CNAME records at the zone apex because the apex must have NS and SOA records, and CNAMEs can't coexist with other records at the same node. However, many DNS providers implement a workaround: they resolve the CNAME target internally and return the resulting A records, making it behave like an ALIAS to the client. Cloudflare calls this CNAME flattening. Route 53 calls these ALIAS records.
The solution:
# Cloudflare: set CNAME with proxied = true, or use CNAME flattening
# Route 53: use ALIAS record type pointing to your hosting provider's endpoint
# Other providers: look for ALIAS or ANAME record types
Common mistake: Switching DNS providers just to avoid this limitation, when the real fix is to use a provider that supports ALIAS/ANAME or CNAME flattening.
11. What's the difference between dns-prefetch and preconnect?
Direct Answer:
dns-prefetch tells the browser to resolve the hostname early. preconnect tells the browser to resolve the hostname, open the TCP connection, and complete TLS negotiation — all before the resource is explicitly requested.
How it works: Both are hints, not instructions. The browser may or may not act on them.
dns-prefetch: does one DNS lookup in the background. Cost: one DNS query. Benefit: eliminates DNS lookup latency when the resource is actually needed (typically 20-100ms).
preconnect: does DNS lookup + TCP handshake + TLS negotiation. Cost: uses a connection slot and some CPU for TLS. Benefit: eliminates all connection setup latency.
The command:
<!-- Use dns-prefetch for third-party domains you'll use but not immediately -->
<link rel="dns-prefetch" href="//cdn.example.com">
<link rel="dns-prefetch" href="//analytics.example.com">
<!-- Use preconnect for critical first-party or third-party resources -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<link rel="preconnect" href="https://api.example.com" crossorigin>
Common mistake:
Using preconnect for too many domains. Each preconnect holds open a TCP connection and uses a TLS negotiation slot. Limit to 2-3 critical origins. For everything else, dns-prefetch is cheaper.
12. Why is Docker DNS resolving differently than my system DNS?
Direct Answer:
Docker uses an embedded DNS resolver at 127.0.0.11 for container-to-container name resolution and service discovery. It doesn't use your system's /etc/resolv.conf directly.
How it works: Docker's embedded DNS server handles lookups for container names and service names within a Docker network. For external hostnames, it forwards to the upstream resolver configured in the Docker daemon or to your system's DNS if not configured.
The embedded resolver (127.0.0.11) does not cache aggressively — it passes through to the upstream resolver for each query. For applications with high external DNS lookup rates, this can add latency.
The command:
# Check what resolver a container is using
docker exec -it <container_name> cat /etc/resolv.conf
# Configure upstream DNS for Docker daemon
# /etc/docker/daemon.json:
{
"dns": ["8.8.8.8", "1.1.1.1"]
}
Common mistake:
Assuming container DNS behaves identically to host DNS. Service names like database resolve correctly inside Docker Compose networks via the embedded DNS but don't exist outside the Docker network.
13. Why can't I use a CNAME for my AWS API Gateway with a custom apex domain?
Direct Answer:
AWS API Gateway provides a CloudFront hostname as its endpoint (e.g., abc123.execute-api.us-east-1.amazonaws.com). You need to point your domain to this hostname. For a subdomain (api.example.com), use a CNAME. For the apex (example.com), use Route 53's ALIAS record type.
How it works: The same apex CNAME problem as Question 10, combined with the fact that AWS API Gateway doesn't provide a static IP address — it uses a hostname. You can't create an A record to a hostname directly in standard DNS. Route 53's ALIAS record type is specifically designed for this: it resolves the target hostname and serves the resulting IP addresses, while appearing as an A record to clients.
The command (Route 53 ALIAS for apex):
# Terraform
resource "aws_route53_record" "apex_api" {
zone_id = data.aws_route53_zone.primary.zone_id
name = "example.com"
type = "A"
alias {
name = aws_api_gateway_domain_name.main.regional_domain_name
zone_id = aws_api_gateway_domain_name.main.regional_zone_id
evaluate_target_health = true
}
}
Common mistake: Trying to use Route 53 ALIAS records outside Route 53. ALIAS is a Route 53-specific extension. If you're using Cloudflare, use their CNAME flattening (root domain CNAME). If you're using another provider, check whether they support ANAME or ALIAS records.
14. How do I set up DNS for a Node.js application on a server?
Direct Answer: Create an A record pointing your domain to the server's IP address. If the server IP changes frequently, consider using a dynamic DNS service or putting a static IP/load balancer in front of the server.
How it works: Your Node.js application listens on a port (typically 3000 or 8080). In front of it, you'll usually have Nginx or a similar reverse proxy on port 80/443 that forwards requests to Node. DNS points to the server's IP. The reverse proxy handles HTTP/HTTPS and routes to Node on the internal port.
The setup:
# DNS records
example.com. A 203.0.113.10
www.example.com. CNAME example.com.
# Nginx reverse proxy config
server {
listen 443 ssl;
server_name example.com www.example.com;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Common mistake: Running Node directly on port 80/443 without a reverse proxy. This works but means your Node process needs to run as root (to bind to ports below 1024), which is a security issue. Use a reverse proxy.
15. How do I set up wildcard DNS for local development?
Direct Answer:
Use dnsmasq to resolve a wildcard local TLD to localhost, or use *.lvh.me which already resolves to 127.0.0.1.
How it works:
dnsmasq is a lightweight DNS server that can be configured to resolve specific patterns to specific addresses. Running it locally and pointing your system resolver to it gives you full wildcard DNS for development.
The setup (macOS with Homebrew):
# Install dnsmasq
brew install dnsmasq
# Configure wildcard for .test TLD
echo "address=/.test/127.0.0.1" > $(brew --prefix)/etc/dnsmasq.conf
# Start dnsmasq
sudo brew services start dnsmasq
# Tell macOS to use dnsmasq for .test domains only
sudo mkdir -p /etc/resolver
echo "nameserver 127.0.0.1" | sudo tee /etc/resolver/test
# Now any *.test domain resolves to localhost
# app.test, myapp.test, api.myapp.test — all hit localhost
Linux (systemd-resolved + dnsmasq):
# /etc/dnsmasq.d/dev.conf
address=/.test/127.0.0.1
Common mistake:
Using .local as the wildcard TLD on macOS. macOS uses Multicast DNS for .local resolution (Bonjour), which conflicts with dnsmasq. Use .test, .localhost, or .example instead.