<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>dns · wleeaf field notes</title><link>https://articles.wleeaf.dev/tags/dns/</link><description>Hands-on guides and build logs from the wleeaf studio: self-hosting, Linux, networking, Docker, the command line and reverse engineering.</description><language>en-us</language><lastBuildDate>Mon, 06 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://articles.wleeaf.dev/tags/dns/feed.xml" rel="self" type="application/rss+xml"/><item><title>DNS For People Who Just Want Their Site to Work</title><link>https://articles.wleeaf.dev/posts/dns-explained-for-developers/</link><pubDate>Mon, 06 Apr 2026 00:00:00 +0000</pubDate><guid>https://articles.wleeaf.dev/posts/dns-explained-for-developers/</guid><description>A, AAAA, CNAME, MX, TXT, NS, SOA. What they do, when to use them, and how to debug when things break.</description><category>dns</category><category>networking</category><category>web</category><content:encoded><![CDATA[<p>DNS is the thing that turns <code>example.com</code> into <code>93.184.216.34</code>. It sounds simple until you need to actually configure it. Then you are staring at a dashboard full of record types, TTL values, and propagation delays wondering why your site still points to the old server.</p>
<h2 id="the-record-types-that-matter">The Record Types That Matter<a class="anchor" href="#the-record-types-that-matter" aria-label="Link to “The Record Types That Matter”">#</a></h2>
<h3 id="a-and-aaaa">A and AAAA<a class="anchor" href="#a-and-aaaa" aria-label="Link to “A and AAAA”">#</a></h3>
<p>The most basic. Maps a domain to an IP address:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>example.dev.    A      203.0.113.50
example.dev.    AAAA   2001:db8::1</code></pre>
</div>
<p>A is IPv4. AAAA is IPv6. You want both if your server supports v6.</p>
<h3 id="cname">CNAME<a class="anchor" href="#cname" aria-label="Link to “CNAME”">#</a></h3>
<p>An alias. Points one name to another:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>www.example.dev.    CNAME    example.dev.</code></pre>
</div>
<p>This says &ldquo;www is the same as the bare domain.&rdquo; The DNS resolver follows the chain and ends up at the A record for <code>example.dev</code>.</p>
<p>The critical rule: <strong>you cannot have a CNAME alongside other records for the same name.</strong> This means the zone apex (bare domain) usually cannot be a CNAME. Use an A record there instead.</p>
<h3 id="mx">MX<a class="anchor" href="#mx" aria-label="Link to “MX”">#</a></h3>
<p>Where to deliver email:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>example.dev.    MX    10 mail.example.dev.</code></pre>
</div>
<p>The number is priority. Lower means preferred. If you have a backup mail server:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>example.dev.    MX    10 mail.example.dev.
example.dev.    MX    20 backup-mail.example.dev.</code></pre>
</div>
<p>Senders try priority 10 first, fall back to 20 if it is down.</p>
<h3 id="txt">TXT<a class="anchor" href="#txt" aria-label="Link to “TXT”">#</a></h3>
<p>Arbitrary text. Used for verification and email authentication:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>example.dev.    TXT    &#34;v=spf1 a mx ip4:203.0.113.50 ~all&#34;</code></pre>
</div>
<p>SPF tells receiving mail servers which IPs are allowed to send email for your domain. DKIM and DMARC are also TXT records. Google and other services use TXT for domain ownership verification too.</p>
<h3 id="ns">NS<a class="anchor" href="#ns" aria-label="Link to “NS”">#</a></h3>
<p>Nameserver delegation. Usually set by your registrar:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>example.dev.    NS    ada.ns.cloudflare.com.
example.dev.    NS    greg.ns.cloudflare.com.</code></pre>
</div>
<p>This tells the internet &ldquo;Cloudflare knows the DNS records for example.dev.&rdquo; You rarely touch these directly.</p>
<h2 id="debugging-with-dig">Debugging With dig<a class="anchor" href="#debugging-with-dig" aria-label="Link to “Debugging With dig”">#</a></h2>
<p><code>dig</code> is the standard DNS debugging tool:</p>
<div class="code">
<div class="code-head"><span class="code-lang">bash</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Basic lookup</span>
</span></span><span class="line"><span class="cl">dig example.dev
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Specific record type</span>
</span></span><span class="line"><span class="cl">dig MX example.dev
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Short output</span>
</span></span><span class="line"><span class="cl">dig +short example.dev
</span></span><span class="line"><span class="cl"><span class="c1"># 203.0.113.50</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Use a specific DNS server</span>
</span></span><span class="line"><span class="cl">dig @8.8.8.8 example.dev
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Trace the full resolution path</span>
</span></span><span class="line"><span class="cl">dig +trace example.dev</span></span></code></pre></div>
</div>
<p>The <code>+trace</code> option follows the entire DNS chain from the root servers down. This is invaluable when debugging propagation issues.</p>
<h2 id="ttl">TTL<a class="anchor" href="#ttl" aria-label="Link to “TTL”">#</a></h2>
<p>Time To Live. How long DNS resolvers cache a record:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>example.dev.    300    IN    A    203.0.113.50</code></pre>
</div>
<p>300 means 5 minutes. After that, resolvers ask again. Lower TTL means faster propagation when you change records, but more DNS queries hitting your nameservers.</p>
<p>Before a migration:</p>
<ol>
<li>Lower TTL to 60 seconds, 24 hours ahead</li>
<li>Wait for the old TTL to expire</li>
<li>Change the record</li>
<li>Verify</li>
<li>Raise TTL back to 3600 or higher</li>
</ol>
<p>This way, when you change the IP, everyone picks up the new address within a minute.</p>
<h2 id="common-patterns">Common Patterns<a class="anchor" href="#common-patterns" aria-label="Link to “Common Patterns”">#</a></h2>
<h3 id="subdomain-to-a-different-server">Subdomain to a different server<a class="anchor" href="#subdomain-to-a-different-server" aria-label="Link to “Subdomain to a different server”">#</a></h3>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>api.example.com.    A    203.0.113.50</code></pre>
</div>
<h3 id="wildcard">Wildcard<a class="anchor" href="#wildcard" aria-label="Link to “Wildcard”">#</a></h3>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>*.example.com.    A    93.184.216.34</code></pre>
</div>
<p>Matches any subdomain that does not have a more specific record. Useful for catch-all routing.</p>
<h3 id="email-setup">Email setup<a class="anchor" href="#email-setup" aria-label="Link to “Email setup”">#</a></h3>
<p>Minimum viable email DNS:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>example.com.         MX   10 mail.example.com.
mail.example.com.    A    93.184.216.34
example.com.         TXT  &#34;v=spf1 a mx ~all&#34;
_dmarc.example.com.  TXT  &#34;v=DMARC1; p=quarantine&#34;
sel._domainkey.example.com.  TXT  &#34;v=DKIM1; k=rsa; p=MIIBIj...&#34;</code></pre>
</div>
<h3 id="cloudflare-proxy-vs-dns-only">Cloudflare proxy vs DNS-only<a class="anchor" href="#cloudflare-proxy-vs-dns-only" aria-label="Link to “Cloudflare proxy vs DNS-only”">#</a></h3>
<p>If you use Cloudflare:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code># Proxied (orange cloud) - traffic goes through Cloudflare CDN
example.dev.    A    203.0.113.50    (proxied)

# DNS-only (gray cloud) - direct connection to your server
mail.example.dev.    A    203.0.113.50    (DNS-only)</code></pre>
</div>
<p>Proxied records hide your real IP and add DDoS protection. But they break non-HTTP protocols. Mail servers, game servers, and anything that is not HTTP/HTTPS needs DNS-only.</p>
<h2 id="why-is-my-dns-not-working">Why Is My DNS Not Working?<a class="anchor" href="#why-is-my-dns-not-working" aria-label="Link to “Why Is My DNS Not Working?”">#</a></h2>
<p>In order of likelihood:</p>
<ol>
<li>
<p><strong>Propagation delay.</strong> You changed the record 2 minutes ago. Wait for the TTL to expire. Check with <code>dig @8.8.8.8</code> to see what Google&rsquo;s DNS returns.</p>
</li>
<li>
<p><strong>Browser cache.</strong> Chrome caches DNS aggressively. Try <code>chrome://net-internals/#dns</code> and clear it, or just use a different browser.</p>
</li>
<li>
<p><strong>Wrong record type.</strong> You added a CNAME where you needed an A record, or vice versa.</p>
</li>
<li>
<p><strong>Cloudflare proxy interfering.</strong> Your service needs direct access but the record is proxied. Switch to DNS-only.</p>
</li>
<li>
<p><strong>Missing trailing dot.</strong> In some DNS configurations, <code>mail.example.com</code> and <code>mail.example.com.</code> are different. The dot makes it fully qualified. Most providers handle this for you, but raw zone files need it.</p>
</li>
</ol>
<div class="code">
<div class="code-head"><span class="code-lang">bash</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Quick sanity check script</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> record in A AAAA MX TXT CNAME<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;=== </span><span class="nv">$record</span><span class="s2"> ===&#34;</span>
</span></span><span class="line"><span class="cl">  dig +short <span class="nv">$record</span> example.com
</span></span><span class="line"><span class="cl"><span class="k">done</span></span></span></code></pre></div>
</div>
<p>DNS is not complicated once you understand the record types. The frustrating part is always the caching and propagation delays. Lower your TTLs before making changes, use <code>dig</code> to verify, and be patient.</p>
]]></content:encoded></item></channel></rss>