DEV Community

mattleeee
mattleeee

Posted on Originally published at hkcode.dpdns.org

Why You Cannot Put a CNAME on the Apex Domain

The zone apex is not like other names

The apex of a zone — the bare domain itself, hkcode.dpdns.org in this case — is where the DNS machinery for the entire zone lives. It must carry an SOA record and a set of NS records. That single fact is the reason you cannot point the apex at a CNAME, and it is not a limitation of GitHub Pages or of any registrar. It is baked into the DNS standard.

When we set up GitHub Pages for a custom domain, GitHub's documentation asks for a CNAME record. The natural reading is "point the domain at username.github.io." For the apex, that request cannot be satisfied literally. The site ends up at www.hkcode.dpdns.org, and the bare name is handled by a redirect or a set of A/AAAA records. Here is why, and how to wire it up without creating a resolver loop or a broken zone.

Why a CNAME cannot live at the apex

The problem: a CNAME claims the whole name

RFC 1034, section 3.6.2 is explicit: a CNAME record is an alias for a domain name, and that name must not have any other data. The text says that if a CNAME RR is present at a node, no other data should be present. RFC 2181, section 10.1 reinforces it: a CNAME record cannot coexist with any other record type at the same owner name, with a narrow exception for DNSSEC-related records (RRSIG, NSEC, and similar).

So the rule is: at a node, either a CNAME, or everything else. Not both.

Now look at the apex. Every zone needs:

  • An SOA record, which describes the zone's primary server, serial number, refresh/retry/expire timers, and negative-caching TTL.
  • At least one NS record set, listing the authoritative name servers.

Those records must exist at the apex by definition — the SOA names the zone origin, and the NS set tells the world who is authoritative for it. If you also tried to place a CNAME at the apex, you would be asking the zone to hold a CNAME and an SOA and NS records at the same owner name. That directly violates RFC 1034/2181.

What happens if a provider lets you try

Many authoritative servers will simply reject the zone file at load time with an error like CNAME and other data. Some permissive servers accept it, and the result is worse: resolvers receive an answer where the CNAME and the SOA/NS coexist. Behavior becomes undefined. Some resolvers will follow the CNAME and discard the SOA; some will treat the zone as lame. Recursive resolvers that cache the CNAME may return it for queries that should have hit the NS set. You end up with intermittent resolution failures, and the failure mode differs by resolver — the hardest kind of bug to diagnose.

There is also a subtler issue. A CNAME at the apex would alias the entire zone's identity to another name. Queries for the SOA at the apex would be answered with a CNAME, and the resolver would chase it to the target, which has no authority over your zone. The delegation chain breaks.

The MX and TXT trap

Even if the RFC allowed it, the apex usually carries more than SOA and NS. Email routing records (MX) and domain-verification or SPF/DKIM records (TXT) commonly live at the bare domain. A CNAME at the apex would make all of those illegal too. For hkcode.dpdns.org, any TXT record used to verify ownership with a third-party service would collide with the CNAME. This is why the "just CNAME the apex" idea fails beyond the pure DNS mechanics: real zones have real records at the apex.

What "flattening" providers actually do

Several DNS providers (Cloudflare, DNSimple, Route 53 with alias records, and others) offer a record type called ALIAS, ANAME, or a "CNAME flattening" feature. This is the workaround people reach for, but it is not a CNAME at the apex. It is a server-side behavior.

Here is the mechanism. You create an ALIAS/ANAME record at the apex pointing to a target name. The provider's authoritative servers do not publish a CNAME. Instead, when a query for the apex arrives, the authoritative server:

  1. Resolves the target name itself (or uses its cached answer).
  2. Returns the resulting A/AAAA records directly, as if they were A/AAAA records at the apex.

The zone on disk still has SOA and NS at the apex; the ALIAS is a provider-specific synthetic record. To the outside world, the apex answers with address records, not a CNAME, so the RFC constraint is respected. The provider has effectively flattened the indirection.

This works, but it is not portable. The ALIAS record type is not standardized in the way A and CNAME are; its semantics differ between providers. If you export the zone and move to a provider that does not support it, the record either disappears or is written out as a literal CNAME, which then breaks the zone. Also, the target's TTL becomes the provider's resolution TTL, not the original CNAME TTL, so you lose some control over caching.

For GitHub Pages specifically, the target username.github.io resolves to GitHub's CDN addresses. Flattening works, but you are pinning the apex to whatever those addresses are at query time. If GitHub rotates them, your provider's cached resolution handles it, but you are trusting the provider's resolver behavior.

How GitHub Pages handles www versus apex

GitHub Pages has a specific model for custom domains:

  • www subdomain: GitHub recommends a CNAME record pointing www.hkcode.dpdns.org to hkcode.github.io (or username.github.io). This is the canonical setup. GitHub's servers see the Host: www.hkcode.dpdns.org header, match it against the configured custom domain, and serve the site.
  • Apex domain: GitHub documents A records (and AAAA records for IPv6) pointing the apex at GitHub's published IP addresses. These are static addresses that GitHub maintains. The apex is not a CNAME; it is a set of address records.

When you configure a custom domain in the repository settings, GitHub stores it and serves requests whose Host header matches. If you set the custom domain to www.hkcode.dpdns.org, requests to the apex hkcode.dpdns.org will not match unless GitHub also handles the redirect.

GitHub Pages does provide an HTTP-level redirect from the apex to the www host when the custom domain is configured as the www name and the apex resolves to GitHub. The redirect is a 301 served by GitHub's front end, not a DNS-level redirect. For that to work, the apex must still resolve to GitHub's servers — via A/AAAA records — so the request reaches GitHub and gets redirected. If the apex has no records at all, the browser never reaches GitHub and there is no redirect.

So the practical split is:

  • DNS for www → CNAME to username.github.io.
  • DNS for apex → A/AAAA records to GitHub's addresses (or a provider redirect).
  • GitHub config → custom domain set to the www name; GitHub 301s the bare name to www.

The practical setup

Step 1: CNAME the www subdomain

At your DNS provider, create:

www.hkcode.dpdns.org.  3600  IN  CNAME  hkcode.github.io.
Enter fullscreen mode Exit fullscreen mode

No other records at www. If you previously had an A record there, remove it. A CNAME and an A record at the same name is the same violation as at the apex, just at a different node.

Step 2: Handle the apex

Two options, depending on your provider.

Option A — GitHub's A/AAAA records. Create A records (and AAAA if you want IPv6) at the apex pointing to GitHub's published addresses. These are documented by GitHub and change rarely, but check them before you paste. The apex then answers with addresses, and GitHub's front end 301s to www.

Option B — provider redirect. If your provider offers an HTTP redirect service for the apex (some registrars and DNS hosts do), point the apex at that redirect and send it to https://www.hkcode.dpdns.org. This avoids hardcoding GitHub's IPs. The tradeoff is that the redirect is handled by the provider, so you depend on their uptime and their support for HTTPS on the redirect.

Do not use an ALIAS/ANAME at the apex unless you have verified the provider's implementation and are comfortable with the non-portability. It works, but it is a provider feature, not a DNS standard.

Step 3: Verify with code

Do not trust the control panel. Query the records directly and check the invariants. A small script using dnspython catches the common mistakes: a stray CNAME at the apex, a missing SOA, or a CNAME and A record coexisting at www.

import dns.resolver
import dns.exception

APEX = "hkcode.dpdns.org"
WWW = f"www.{APEX}"

def query(name, rtype):
    try:
        answer = dns.resolver.resolve(name, rtype, lifetime=5)
        return [r.to_text() for r in answer]
    except dns.resolver.NoAnswer:
        return []
    except dns.resolver.NXDOMAIN:
        return ["NXDOMAIN"]
    except dns.exception.Timeout:
        return ["TIMEOUT"]

def check_zone(apex, www):
    problems = []

    # The apex must have SOA and NS. A CNAME here is illegal.
    apex_cname = query(apex, "CNAME")
    if apex_cname and apex_cname != ["NXDOMAIN"]:
        problems.append(f"apex has a CNAME: {apex_cname}")

    if not query(apex, "SOA"):
        problems.append("apex has no SOA")
    if not query(apex, "NS"):
        problems.append("apex has no NS")

    # The www node should be a CNAME and nothing else.
    www_cname = query(www, "CNAME")
    www_a = query(www, "A")
    if not www_cname:
        problems.append("www has no CNAME")
    if www_a and www_cname:
        problems.append("www has both CNAME and A records")

    return problems

if __name__ == "__main__":
    issues = check_zone(APEX, WWW)
    if issues:
        for issue in issues:
            print("FAIL:", issue)
    else:
        print("zone looks consistent")
Enter fullscreen mode Exit fullscreen mode

Run it against the authoritative servers, not just your local resolver, so you see what the world sees. You can set the resolver explicitly:

import dns.resolver

resolver = dns.resolver.Resolver(configure=False)
# Use the provider's authoritative nameservers, from the NS set.
resolver.nameservers = ["ns1.example.net", "ns2.example.net"]
resolver.lifetime = 5
Enter fullscreen mode Exit fullscreen mode

Step 4: Check the HTTP redirect

Once DNS is correct, confirm the redirect behavior with a request that does not follow redirects:

import urllib.request
import urllib.error

def check_redirect(url):
    req = urllib.request.Request(url, method="HEAD")
    opener = urllib.request.build_opener(urllib.request.HTTPRedirectHandler)
    try:
        # Prevent automatic following so we can inspect the 301.
        class NoRedirect(urllib.request.HTTPRedirectHandler):
            def redirect_request(self, *args, **kwargs):
                return None
        opener = urllib.request.build_opener(NoRedirect)
        resp = opener.open(req, timeout=10)
        print(url, "->", resp.status)
    except urllib.error.HTTPError as e:
        location = e.headers.get("Location")
        print(url, "->", e.code, "Location:", location)
    except Exception as e:
        print(url, "-> error:", e)

check_redirect("http://hkcode.dpdns.org/")
check_redirect("https://hkcode.dpdns.org/")
Enter fullscreen mode Exit fullscreen mode

You want a 301 (or 308) with Location: https://www.hkcode.dpdns.org/. If you get a connection error, the apex has no address records and the redirect never happens. If you get a 200, GitHub is serving the apex directly — check whether the custom domain in the repo settings is set to the bare name, which changes the canonical host.

Common failure modes

  • CNAME at the apex "because the provider allowed it." The zone loads, but some resolvers fail on the SOA or NS query. Symptom: intermittent SERVFAIL from specific networks.
  • CNAME at www plus an A record "for safety." Same violation, smaller blast radius. Remove the A record.
  • Apex A records but no www CNAME. The site works at the bare name but not at www, or vice versa, and the canonical URL drifts.
  • Provider redirect without HTTPS. The apex redirects over HTTP only, and browsers block or warn. Make sure the redirect target uses https://.
  • Stale TTLs during migration. Lower the TTL on the old records to 300 seconds a day before the change, then raise it after the switch settles.

Summary

A CNAME at the apex is not a configuration choice; it is a contradiction of the zone's required SOA and NS records under RFC 1034 and 2181. ALIAS/ANAME records are a provider-side flattening trick that returns address records instead of a CNAME, and they work but do not travel. GitHub Pages expects a CNAME on www and either A/AAAA records or a provider redirect on the apex, with an HTTP 301 from the bare name to www. Set the CNAME on the subdomain, give the apex address records or a redirect, and verify both the DNS invariants and the redirect with a script rather than the control panel.

More notes like this ship every week on this site.


Daily Picks

The following pairs are selected from the multi-timeframe trend scanner (Gate.io futures) and are for technical-analysis study only — not investment advice.
Data updated: 2026-10-23 08:46:52

Long

Pair Signal Price Take Profit Stop Loss R/R
BTW 高质量标的做多 $1.8281 $1.9561 $1.7641 1:2.0

1 picks selected. Scanner runs every 15 minutes.

Top comments (0)