
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[ The Cloudflare Blog ]]></title>
        <description><![CDATA[ Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet. ]]></description>
        <link>https://blog.cloudflare.com</link>
        <atom:link href="https://blog.cloudflare.com/" rel="self" type="application/rss+xml"/>
        <language>en-us</language>
        <image>
            <url>https://blog.cloudflare.com/favicon.png</url>
            <title>The Cloudflare Blog</title>
            <link>https://blog.cloudflare.com</link>
        </image>
        <lastBuildDate>Thu, 09 Apr 2026 00:10:45 GMT</lastBuildDate>
        <item>
            <title><![CDATA[What came first: the CNAME or the A record?]]></title>
            <link>https://blog.cloudflare.com/cname-a-record-order-dns-standards/</link>
            <pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[ A recent change to 1.1.1.1 accidentally altered the order of CNAME records in DNS responses, breaking resolution for some clients. This post explores the technical root cause, examines the source code of affected resolvers, and dives into the inherent ambiguities of the DNS RFCs.   ]]></description>
            <content:encoded><![CDATA[ <p>On January 8, 2026, a routine update to 1.1.1.1 aimed at reducing memory usage accidentally triggered a wave of DNS resolution failures for users across the Internet. The root cause wasn't an attack or an outage, but a subtle shift in the order of records within our DNS responses.  </p><p>While most modern software treats the order of records in DNS responses as irrelevant, we discovered that some implementations expect CNAME records to appear before everything else. When that order changed, resolution started failing. This post explores the code change that caused the shift, why it broke specific DNS clients, and the 40-year-old protocol ambiguity that makes the "correct" order of a DNS response difficult to define.</p>
    <div>
      <h2>Timeline</h2>
      <a href="#timeline">
        
      </a>
    </div>
    <p><i>All timestamps referenced are in Coordinated Universal Time (UTC).</i></p><table><tr><th><p><b>Time</b></p></th><th><p><b>Description</b></p></th></tr><tr><td><p>2025-12-02</p></td><td><p>The record reordering is introduced to the 1.1.1.1 codebase</p></td></tr><tr><td><p>2025-12-10</p></td><td><p>The change is released to our testing environment</p></td></tr><tr><td><p>2026-01-07 23:48</p></td><td><p>A global release containing the change starts</p></td></tr><tr><td><p>2026-01-08 17:40</p></td><td><p>The release reaches 90% of servers</p></td></tr><tr><td><p>2026-01-08 18:19</p></td><td><p>Incident is declared</p></td></tr><tr><td><p>2026-01-08 18:27</p></td><td><p>The release is reverted</p></td></tr><tr><td><p>2026-01-08 19:55</p></td><td><p>Revert is completed. Impact ends</p></td></tr></table>
    <div>
      <h2>What happened?</h2>
      <a href="#what-happened">
        
      </a>
    </div>
    <p>While making some improvements to lower the memory usage of our cache implementation, we introduced a subtle change to CNAME record ordering. The change was introduced on December 2, 2025, released to our testing environment on December 10, and began deployment on January 7, 2026.</p>
    <div>
      <h3>How DNS CNAME chains work</h3>
      <a href="#how-dns-cname-chains-work">
        
      </a>
    </div>
    <p>When you query for a domain like <code>www.example.com</code>, you might get a <a href="https://www.cloudflare.com/learning/dns/dns-records/dns-cname-record/"><u>CNAME (Canonical Name)</u></a> record that indicates one name is an alias for another name. It’s the job of public resolvers, such as <a href="https://www.cloudflare.com/learning/dns/what-is-1.1.1.1/"><u>1.1.1.1</u></a>, to follow this chain of aliases until it reaches a final response:</p><p><code>www.example.com → cdn.example.com → server.cdn-provider.com → 198.51.100.1</code></p><p>As 1.1.1.1 traverses this chain, it caches every intermediate record. Each record in the chain has its own <a href="https://www.cloudflare.com/learning/cdn/glossary/time-to-live-ttl/"><u>TTL (Time-To-Live)</u></a>, indicating how long we can cache it. Not all the TTLs in a CNAME chain need to be the same:</p><p><code>www.example.com → cdn.example.com (TTL: 3600 seconds) # Still cached
cdn.example.com → 198.51.100.1    (TTL: 300 seconds)  # Expired</code></p><p>When one or more records in a CNAME chain expire, it’s considered partially expired. Fortunately, since parts of the chain are still in our cache, we don’t have to resolve the entire CNAME chain again — only the part that has expired. In our example above, we would take the still valid <code>www.example.com → cdn.example.com</code> chain, and only resolve the expired <code>cdn.example.com</code> <a href="https://www.cloudflare.com/learning/dns/dns-records/dns-a-record/"><u>A record</u></a>. Once that’s done, we combine the existing CNAME chain and the newly resolved records into a single response.</p>
    <div>
      <h3>The logic change</h3>
      <a href="#the-logic-change">
        
      </a>
    </div>
    <p>The code that merges these two chains is where the change occurred. Previously, the code would create a new list, insert the existing CNAME chain, and then append the new records:</p>
            <pre><code>impl PartialChain {
    /// Merges records to the cache entry to make the cached records complete.
    pub fn fill_cache(&amp;self, entry: &amp;mut CacheEntry) {
        let mut answer_rrs = Vec::with_capacity(entry.answer.len() + self.records.len());
        answer_rrs.extend_from_slice(&amp;self.records); // CNAMEs first
        answer_rrs.extend_from_slice(&amp;entry.answer); // Then A/AAAA records
        entry.answer = answer_rrs;
    }
}
</code></pre>
            <p>However, to save some memory allocations and copies, the code was changed to instead append the CNAMEs to the existing answer list:</p>
            <pre><code>impl PartialChain {
    /// Merges records to the cache entry to make the cached records complete.
    pub fn fill_cache(&amp;self, entry: &amp;mut CacheEntry) {
        entry.answer.extend(self.records); // CNAMEs last
    }
}
</code></pre>
            <p>As a result, the responses that 1.1.1.1 returned now sometimes had the CNAME records appearing at the bottom, after the final resolved answer.</p>
    <div>
      <h3>Why this caused impact</h3>
      <a href="#why-this-caused-impact">
        
      </a>
    </div>
    <p>When DNS clients receive a response with a CNAME chain in the answer section, they also need to follow this chain to find out that <code>www.example.com</code> points to <code>198.51.100.1</code>. Some DNS client implementations handle this by keeping track of the expected name for the records as they’re iterated sequentially. When a CNAME is encountered, the expected name is updated:</p>
            <pre><code>;; QUESTION SECTION:
;; www.example.com.        IN    A

;; ANSWER SECTION:
www.example.com.    3600   IN    CNAME  cdn.example.com.
cdn.example.com.    300    IN    A      198.51.100.1
</code></pre>
            <p></p><ol><li><p>Find records for <code>www.example.com</code></p></li><li><p>Encounter <code>www.example.com. CNAME cdn.example.com</code></p></li><li><p>Find records for <code>cdn.example.com</code></p></li><li><p>Encounter <code>cdn.example.com. A 198.51.100.1</code></p></li></ol><p>When the CNAME suddenly appears at the bottom, this no longer works:</p>
            <pre><code>;; QUESTION SECTION:
;; www.example.com.	       IN    A

;; ANSWER SECTION:
cdn.example.com.    300    IN    A      198.51.100.1
www.example.com.    3600   IN    CNAME  cdn.example.com.
</code></pre>
            <p></p><ol><li><p>Find records for <code>www.example.com</code></p></li><li><p>Ignore <code>cdn.example.com. A 198.51.100.1</code> as it doesn’t match the expected name</p></li><li><p>Encounter <code>www.example.com. CNAME cdn.example.com</code></p></li><li><p>Find records for <code>cdn.example.com</code></p></li><li><p>No more records are present, so the response is considered empty</p></li></ol><p>One such implementation that broke is the <a href="https://man7.org/linux/man-pages/man3/getaddrinfo.3.html"><code><u>getaddrinfo</u></code></a> function in glibc, which is commonly used on Linux for DNS resolution. When looking at its <code>getanswer_r</code> implementation, we can indeed see it expects to find the CNAME records before any answers:</p>
            <pre><code>for (; ancount &gt; 0; --ancount)
  {
    // ... parsing DNS records ...
    
    if (rr.rtype == T_CNAME)
      {
        /* Record the CNAME target as the new expected name. */
        int n = __ns_name_unpack (c.begin, c.end, rr.rdata,
                                  name_buffer, sizeof (name_buffer));
        expected_name = name_buffer;  // Update what we're looking for
      }
    else if (rr.rtype == qtype
             &amp;&amp; __ns_samebinaryname (rr.rname, expected_name)  // Must match!
             &amp;&amp; rr.rdlength == rrtype_to_rdata_length (type:qtype))
      {
        /* Address record matches - store it */
        ptrlist_add (list:addresses, item:(char *) alloc_buffer_next (abuf, uint32_t));
        alloc_buffer_copy_bytes (buf:abuf, src:rr.rdata, size:rr.rdlength);
      }
  }
</code></pre>
            <p>Another notable affected implementation was the DNSC process in three models of Cisco ethernet switches. In the case where switches had been configured to use 1.1.1.1 these switches experienced spontaneous reboot loops when they received a response containing the reordered CNAMEs. <a href="https://www.cisco.com/c/en/us/support/docs/smb/switches/Catalyst-switches/kmgmt3846-cbs-reboot-with-fatal-error-from-dnsc-process.html"><u>Cisco has published a service document describing the issue</u></a>.</p>
    <div>
      <h3>Not all implementations break</h3>
      <a href="#not-all-implementations-break">
        
      </a>
    </div>
    <p>Most DNS clients don’t have this issue. For example, <a href="https://www.freedesktop.org/software/systemd/man/latest/systemd-resolved.service.html"><u>systemd-resolved</u></a> first parses the records into an ordered set:</p>
            <pre><code>typedef struct DnsAnswerItem {
        DnsResourceRecord *rr; // The actual record
        DnsAnswerFlags flags;  // Which section it came from
        // ... other metadata
} DnsAnswerItem;


typedef struct DnsAnswer {
        unsigned n_ref;
        OrderedSet *items;
} DnsAnswer;
</code></pre>
            <p>When following a CNAME chain it can then search the entire answer set, even if the CNAME records don’t appear at the top.</p>
    <div>
      <h2>What the RFC says</h2>
      <a href="#what-the-rfc-says">
        
      </a>
    </div>
    <p><a href="https://datatracker.ietf.org/doc/html/rfc1034"><u>RFC 1034</u></a>, published in 1987, defines much of the behavior of the DNS protocol, and should give us an answer on whether the order of CNAME records matters. <a href="https://datatracker.ietf.org/doc/html/rfc1034#section-4.3.1"><u>Section 4.3.1</u></a> contains the following text:</p><blockquote><p>If recursive service is requested and available, the recursive response to a query will be one of the following:</p><p>- The answer to the query, possibly preface by one or more CNAME RRs that specify aliases encountered on the way to an answer.</p></blockquote><p>While "possibly preface" can be interpreted as a requirement for CNAME records to appear before everything else, it does not use normative key words, such as <a href="https://datatracker.ietf.org/doc/html/rfc2119"><u>MUST and SHOULD</u></a> that modern RFCs use to express requirements. This isn’t a flaw in RFC 1034, but simply a result of its age. <a href="https://datatracker.ietf.org/doc/html/rfc2119"><u>RFC 2119</u></a>, which standardized these key words, was published in 1997, 10 years <i>after</i> RFC 1034.</p><p>In our case, we did originally implement the specification so that CNAMEs appear first. However, we did not have any tests asserting the behavior remains consistent due to the ambiguous language in the RFC.</p>
    <div>
      <h3>The subtle distinction: RRsets vs RRs in message sections</h3>
      <a href="#the-subtle-distinction-rrsets-vs-rrs-in-message-sections">
        
      </a>
    </div>
    <p>To understand why this ambiguity exists, we need to understand a subtle but important distinction in DNS terminology.</p><p>RFC 1034 <a href="https://datatracker.ietf.org/doc/html/rfc1034#section-3.6"><u>section 3.6</u></a> defines Resource Record Sets (RRsets) as collections of records with the same name, type, and class. For RRsets, the specification is clear about ordering:</p><blockquote><p>The order of RRs in a set is not significant, and need not be preserved by name servers, resolvers, or other parts of the DNS.</p></blockquote><p>However, RFC 1034 doesn’t clearly specify how message sections relate to RRsets. While modern DNS specifications have shown that message sections can indeed contain multiple RRsets (consider <a href="https://www.cloudflare.com/learning/dns/dnssec/how-dnssec-works/">DNSSEC</a> responses with signatures), RFC 1034 doesn’t describe message sections in those terms. Instead, it treats message sections as containing individual Resource Records (RRs).</p><p>The problem is that the RFC primarily discusses ordering in the context of RRsets but doesn't specify the ordering of different RRsets relative to each other within a message section. This is where the ambiguity lives.</p><p>RFC 1034 <a href="https://datatracker.ietf.org/doc/html/rfc1034#section-6.2.1"><u>section 6.2.1</u></a> includes an example that demonstrates this ambiguity further. It mentions that the order of Resource Records (RRs) is not significant either:</p><blockquote><p>The difference in ordering of the RRs in the answer section is not significant.</p></blockquote><p>However, this example only shows two A records for the same name within the same RRset. It doesn't address whether this applies to different record types like CNAMEs and A records.</p>
    <div>
      <h2>CNAME chain ordering</h2>
      <a href="#cname-chain-ordering">
        
      </a>
    </div>
    <p>It turns out that this issue extends beyond putting CNAME records before other record types. Even when CNAMEs appear before other records, sequential parsing can still break if the CNAME chain itself is out of order. Consider the following response:</p>
            <pre><code>;; QUESTION SECTION:
;; www.example.com.              IN    A

;; ANSWER SECTION:
cdn.example.com.           3600  IN    CNAME  server.cdn-provider.com.
www.example.com.           3600  IN    CNAME  cdn.example.com.
server.cdn-provider.com.   300   IN    A      198.51.100.1
</code></pre>
            <p>Each CNAME belongs to a different RRset, as they have different owners, so the statement about RRset order being insignificant doesn’t apply here.</p><p>However, RFC 1034 doesn't specify that CNAME chains must appear in any particular order. There's no requirement that <code>www.example.com. CNAME cdn.example.com.</code> must appear before <code>cdn.example.com. CNAME server.cdn-provider.com.</code>. With sequential parsing, the same issue occurs:</p><ol><li><p>Find records for <code>www.example.com</code></p></li><li><p>Ignore <code>cdn.example.com. CNAME server.cdn-provider.com</code>. as it doesn’t match the expected name</p></li><li><p>Encounter <code>www.example.com. CNAME cdn.example.com</code></p></li><li><p>Find records for <code>cdn.example.com</code></p></li><li><p>Ignore <code>server.cdn-provider.com. A 198.51.100.1</code> as it doesn’t match the expected name</p></li></ol>
    <div>
      <h2>What should resolvers do?</h2>
      <a href="#what-should-resolvers-do">
        
      </a>
    </div>
    <p>RFC 1034 section 5 describes resolver behavior. <a href="https://datatracker.ietf.org/doc/html/rfc1034#section-5.2.2"><u>Section 5.2.2</u></a> specifically addresses how resolvers should handle aliases (CNAMEs): </p><blockquote><p>In most cases a resolver simply restarts the query at the new name when it encounters a CNAME.</p></blockquote><p>This suggests that resolvers should restart the query upon finding a CNAME, regardless of where it appears in the response. However, it's important to distinguish between different types of resolvers:</p><ul><li><p>Recursive resolvers, like 1.1.1.1, are full DNS resolvers that perform recursive resolution by querying authoritative nameservers</p></li><li><p>Stub resolvers, like glibc’s getaddrinfo, are simplified local interfaces that forward queries to recursive resolvers and process the responses</p></li></ul><p>The RFC sections on resolver behavior were primarily written with full resolvers in mind, not the simplified stub resolvers that most applications actually use. Some stub resolvers evidently don’t implement certain parts of the spec, such as the CNAME-restart logic described in the RFC. </p>
    <div>
      <h2>The DNSSEC specifications provide contrast</h2>
      <a href="#the-dnssec-specifications-provide-contrast">
        
      </a>
    </div>
    <p>Later DNS specifications demonstrate a different approach to defining record ordering. <a href="https://datatracker.ietf.org/doc/html/rfc4035"><u>RFC 4035</u></a>, which defines protocol modifications for <a href="https://www.cloudflare.com/learning/dns/dnssec/how-dnssec-works/"><u>DNSSEC</u></a>, uses more explicit language:</p><blockquote><p>When placing a signed RRset in the Answer section, the name server MUST also place its RRSIG RRs in the Answer section. The RRSIG RRs have a higher priority for inclusion than any other RRsets that may have to be included.</p></blockquote><p>The specification uses "MUST" and explicitly defines "higher priority" for <a href="https://www.cloudflare.com/learning/dns/dnssec/how-dnssec-works/"><u>RRSIG</u></a> records. However, "higher priority for inclusion" refers to whether RRSIGs should be included in the response, not where they should appear. This provides unambiguous guidance to implementers about record inclusion in DNSSEC contexts, while not mandating any particular behavior around record ordering.</p><p>For unsigned zones, however, the ambiguity from RFC 1034 remains. The word "preface" has guided implementation behavior for nearly four decades, but it has never been formally specified as a requirement.</p>
    <div>
      <h2>Do CNAME records come first?</h2>
      <a href="#do-cname-records-come-first">
        
      </a>
    </div>
    <p>While in our interpretation the RFCs do not require CNAMEs to appear in any particular order, it’s clear that at least some widely-deployed DNS clients rely on it. As some systems using these clients might be updated infrequently, or never updated at all, we believe it’s best to require CNAME records to appear in-order before any other records.</p><p>Based on what we have learned during this incident, we have reverted the CNAME re-ordering and do not intend to change the order in the future.</p><p>To prevent any future incidents or confusion, we have written a proposal in the form of an <a href="https://www.ietf.org/participate/ids/"><u>Internet-Draft</u></a> to be discussed at the IETF. If consensus is reached on the clarified behavior, this would become an RFC that explicitly defines how to correctly handle CNAMEs in DNS responses, helping us and the wider DNS community navigate the protocol. The proposal can be found at <a href="https://datatracker.ietf.org/doc/draft-jabley-dnsop-ordered-answer-section/">https://datatracker.ietf.org/doc/draft-jabley-dnsop-ordered-answer-section</a>. If you have suggestions or feedback we would love to hear your opinions, most usefully via the <a href="https://datatracker.ietf.org/wg/dnsop/about/"><u>DNSOP working group</u></a> at the IETF.</p> ]]></content:encoded>
            <category><![CDATA[1.1.1.1]]></category>
            <category><![CDATA[Post Mortem]]></category>
            <category><![CDATA[DNS]]></category>
            <category><![CDATA[Resolver]]></category>
            <category><![CDATA[Standards]]></category>
            <category><![CDATA[Bugs]]></category>
            <category><![CDATA[Consumer Services]]></category>
            <guid isPermaLink="false">3fP84BsxwSxKr7ffpmVO6s</guid>
            <dc:creator>Sebastiaan Neuteboom</dc:creator>
        </item>
    </channel>
</rss>