GitHub Pages Custom Domain SEO: DNS, HTTPS and Redirects
Move a GitHub Pages blog to a custom domain without losing search traffic: www or apex, the exact DNS records, the CNAME file with branch and Actions deploys, domain verification, Enforce HTTPS, the redirects GitHub makes for you, and a curl check.
Why the custom domain step matters for search
A blog on username.github.io works, but it builds search history on an address you do not own. Most teams move to their own domain sooner or later, and that move is where GitHub Pages blogs lose traffic: a site that answers on four hostnames, a certificate that never gets issued, a sitemap still pointing at github.io, or a canonical tag that sends every signal back to the old address.
None of this is hard once you know what GitHub does for you and what it leaves to you. This guide walks through the setup in the order you would do it, with the exact records and settings from GitHub's documentation, and ends with a curl check you can run after every change. It applies to any generator that deploys to GitHub Pages: Jekyll, Hugo, Astro, Next.js static export, Gatsby or Docusaurus.
If you are on GitLab instead, the GitLab Pages SEO checklist covers the same ground there, including its primary domain redirect.
Step 1: Pick www or the apex domain, then never serve both
You can use three kinds of custom domain on GitHub Pages: a www subdomain (www.example.com), another subdomain (blog.example.com) or the apex (example.com). GitHub's custom domains overview recommends always setting up the www subdomain, even if you also use the apex, because a www CNAME is not affected if GitHub's server IP addresses change.
For search, the choice between www and the apex barely matters. What matters is that one hostname is the real one and every other version redirects to it. Google's guide to consolidating duplicate URLs treats redirects as a strong canonical signal and sitemap inclusion as only a weak one, and it states no preference between www and non-www.
GitHub handles the pair for you: if both the apex and www have the correct DNS records, it redirects between them automatically, toward whichever one you entered as the custom domain. So decide once:
www.example.comas the custom domain.example.comredirects to it. The safest DNS setup.example.comas the custom domain.www.example.comredirects to it. Shorter URLs, slightly more DNS to maintain.blog.example.com. Common when the product site lives elsewhere. Note that search engines treat a subdomain as a separate host, so the blog's authority does not automatically flow toexample.com.
Whichever you choose, write it down. You will type it into the Pages settings, your generator config, your sitemap and Search Console, and they must all agree.
Step 2: Add the DNS records
These values come from GitHub's managing a custom domain page, checked on the date of this post.
| Host | Type | Value |
|---|---|---|
@ (apex) | A | 185.199.108.153, 185.199.109.153, 185.199.110.153, 185.199.111.153 |
@ (apex) | AAAA | 2606:50c0:8000::153, 2606:50c0:8001::153, 2606:50c0:8002::153, 2606:50c0:8003::153 |
@ (apex, alternative) | ALIAS or ANAME | username.github.io |
www | CNAME | username.github.io |
blog (other subdomain) | CNAME | username.github.io |
Three details trip people up:
- The CNAME target is your account's default domain, without the repository name. For a project repo called
docs-siteunderacme, the target is stillacme.github.io, notacme.github.io/docs-site. Do not use the unique*.pages.github.ioaddress shown in the settings either. - Remove every other record on those hosts. Leftover A or CNAME records from a previous host, often a parking page from the registrar, are the most common reason the HTTPS certificate is never issued. GitHub's HTTPS page lists extra records pointing elsewhere as a cause of certificate failures.
- No wildcard records. GitHub warns that a record like
*.example.comcreates an immediate takeover risk, even if the domain is verified.
Check the records before touching GitHub:
dig example.com +noall +answer -t A
dig example.com +noall +answer -t AAAA
dig www.example.com +nostats +nocomments +nocmd
You want to see the four GitHub IPs for the apex and a CNAME to username.github.io for www, and nothing else. DNS changes can take up to 24 hours to propagate.
Step 3: Set the domain in the repository, and know where the CNAME file lives
In the repository, open Settings > Pages, type the domain under Custom domain and save. What happens next depends on how the site is published:
- Deploy from a branch. Saving creates a commit that adds a
CNAMEfile to the root of the publishing source. Keep that file. If your generator wipes the output folder on each build, putCNAMEin the folder it copies verbatim (static/in Hugo, Docusaurus and Gatsby,public/in Astro and Next.js, the site root in Jekyll), or the next deploy removes your domain. - A custom GitHub Actions workflow (the
actions/deploy-pagesroute most non-Jekyll sites use). NoCNAMEfile is created, and an existing one is ignored and not required. The domain lives in the repository settings only.
Mixing the two causes confusion: a team switches from branch deploys to Actions, deletes the CNAME file because "it does nothing now", and that is correct, but if they later switch back, the first branch deploy without it drops the domain.
Step 4: Verify the domain to prevent takeover
This step is not about rankings, but skipping it can cost you all of them. GitHub's domain verification guide explains that without verification, another GitHub user could publish their own site on your domain if your repository is deleted, your plan is downgraded, or Pages is turned off while the DNS still points at GitHub. A spam site on your blog's hostname is the fastest way to lose its search history.
Verification is done in your account or organization settings, not the repository: Settings > Pages > Add a domain. GitHub gives you a TXT record named like this:
_github-pages-challenge-USERNAME.example.com
Add it, wait for it to resolve, click Verify, and leave the record in DNS for good. Verification also covers immediate subdomains, so verifying example.com protects www.example.com and blog.example.com.
Step 5: Turn on Enforce HTTPS
Once the DNS check passes, GitHub requests a certificate from Let's Encrypt and installs it; a check mark appears next to the domain. Then tick Enforce HTTPS in the same Pages settings. It can take up to 24 hours before the option becomes available.
Enforce HTTPS is the setting that makes http:// redirect to https://. Without it, both versions answer with a 200, and you have two copies of every page. Google prefers HTTPS as canonical when it can, but an invalid certificate or an HTTPS page that redirects to HTTP overrides that preference, so a half-finished setup can leave Google picking the HTTP copy.
This is not a theoretical problem. When we checked a well-known project documentation site on GitHub Pages while writing this post, its http:// address returned 200 OK instead of a redirect, so both protocols serve the same pages. Run the check in Step 8 on your own site rather than assuming.
If the certificate is stuck at "not yet created":
- Click Remove next to the custom domain, type it again and save. That restarts provisioning.
- Re-check DNS for stray records (Step 2).
- Keep the full hostname under 64 characters; the certificate's common name cannot be longer.
After HTTPS is on, fix mixed content: any image, script or stylesheet still loaded over http:// triggers browser warnings. Search the source for http:// and change those references. In Jekyll, start with _layouts and _includes.
Step 6: Point your generator at the new domain
GitHub redirects requests, but it does not change what is inside your HTML. Canonical tags, og:url, sitemap entries and RSS links are all built from a base URL in your generator config, and if that still says github.io, every page tells Google the old address is the real one.
| Generator | Setting | Value |
|---|---|---|
| Jekyll | url and baseurl in _config.yml | url: "https://www.example.com", baseurl: "" |
| Hugo | baseURL in hugo.toml | "https://www.example.com/" |
| Astro | site in astro.config.mjs (and remove base if you used one for /repo/) | "https://www.example.com" |
| Next.js (static export) | metadataBase in the root layout; remove basePath if it was the repo name | new URL("https://www.example.com") |
| Gatsby | siteMetadata.siteUrl; drop pathPrefix | "https://www.example.com" |
| Docusaurus | url and baseUrl in docusaurus.config.js | url: "https://www.example.com", baseUrl: "/" |
The base path is the part people forget. A project site served at acme.github.io/docs-site/ needs baseurl: /docs-site (or basePath, base, pathPrefix), and the same value on a custom domain moves every asset and link to a folder that no longer exists. The Jekyll SEO checklist walks through url and baseurl in detail; the Hugo SEO checklist does the same for baseURL.
Also update robots.txt so its Sitemap: line uses the new host. A project site's robots.txt was never read anyway, because crawlers only read it from the root of a host; moving to a custom domain is the first time it counts.
Step 7: Understand the redirects you get, and the ones you do not
After the move, GitHub gives you three redirects without any configuration:
github.ioto your custom domain. We checked a project site while writing this:https://jekyll.github.io/jekyll/docs/answered with301and aLocationon the custom domain at the same path,/docs/. The path is kept, so deep links and old backlinks land on the right post.- Apex to
www, or the reverse, when both have DNS records (Step 1). httptohttps, but only with Enforce HTTPS on (Step 5).
What you do not get is any server-side redirect rule of your own. GitHub Pages has no _redirects file and no .htaccess, so if the move also changes post URLs, for example a new permalink style, the old paths will 404. Jekyll sites can use jekyll-redirect-from, and other generators have similar plugins, but they all write an HTML page with a meta refresh rather than a real 301. Google's redirects guide says it reads an instant meta refresh as a permanent redirect, so it works, but the cleaner path is to keep post URLs the same and change only the host.
Google's site move guide asks you to keep redirects in place for at least a year, and to expect rankings to move around for a few weeks while the new URLs replace the old ones. On GitHub Pages that mostly means: do not delete the repository or turn Pages off on the old address.
Search Console
Add the new domain to Google Search Console and submit the new sitemap. The Change of Address tool is for moves between domains and requires you to own both properties. It cannot move properties at the path level, so a project site at username.github.io/repo/ cannot use it; the 301s do the work there. It is also not used for http to https or www to non-www changes.
Step 8: Check it from the outside
Settings screens tell you what GitHub intends. These commands tell you what a crawler sees (the free GitHub Pages custom domain check fills in your domain, old address and post path for you):
DOMAIN="www.example.com"
OLD="https://username.github.io/repo"
POST="/blog/my-post/"
# 1. The real URL answers 200 over HTTPS
curl -sI "https://$DOMAIN$POST" | head -1
# 2. http redirects to https (needs Enforce HTTPS)
curl -sI "http://$DOMAIN$POST" | grep -iE '^(HTTP|location)'
# 3. The other hostname redirects to the chosen one
curl -sI "https://example.com$POST" | grep -iE '^(HTTP|location)'
# 4. The old github.io address redirects, keeping the path
curl -sI "$OLD$POST" | grep -iE '^(HTTP|location)'
# 5. The canonical tag uses the new host
curl -s "https://$DOMAIN$POST" | grep -o '<link rel="canonical"[^>]*>'
# 6. The sitemap lists the new host only
curl -s "https://$DOMAIN/sitemap.xml" | grep -c 'github.io'
What good looks like: a 200 on line 1, a 301 with an https:// location on lines 2 to 4, a canonical on your domain, and 0 on the last line. If check 4 points at http:// rather than https://, there is an extra hop before the final URL; Google's guide asks for redirect chains of no more than three hops, so it is fine, but checks 2 and 4 together show whether Enforce HTTPS is on.
Common mistakes to check for
- Generator base URL still on
github.io. Pages redirect, but every canonical tag points backwards. - Enforce HTTPS never ticked because the option was greyed out on day one and nobody came back after the certificate was issued.
- Leftover registrar records blocking the certificate.
CNAMEfile deleted by a build that cleans its output folder, on branch deploys.- Unverified domain, which is fine until the day the repository is renamed or removed.
- Old
basePathkept, so CSS and links point at/repo/...on the new domain.
FAQ
Does a custom domain help SEO on GitHub Pages?
Not by itself, since Google ranks pages, not domain names. It helps because the authority your posts earn stays on an address you control, links to your blog also strengthen your product's brand, and, for a former project site, robots.txt finally sits at the root of the host where crawlers read it.
Should I use www or the apex domain on GitHub Pages?
Either works for search, as long as one redirects to the other. GitHub recommends setting up www because its CNAME does not depend on GitHub's IP addresses, and redirects between the two automatically when both have records.
Do I need a CNAME file in my repository?
Only if you deploy from a branch. With a custom GitHub Actions workflow the domain is stored in the repository settings, and a CNAME file is ignored.
How long does HTTPS take on a new custom domain?
DNS can take up to 24 hours to propagate, and the Enforce HTTPS option can take up to 24 hours to appear. If the certificate is still missing after DNS is correct, remove the domain and add it again to restart provisioning.
Will my github.io URLs keep working?
Yes. With a custom domain set, GitHub answers requests on the old github.io address with a 301 to the same path on your domain, as long as the repository and its Pages site stay in place.
Keep the posts coming
A blog on its own domain with clean redirects is ready to earn traffic, but only new posts give it something to rank. Blogger Dog researches and writes cited SEO and AEO posts and commits each one to your GitHub repo on the branch you deploy from, so your Pages build runs exactly as it does for your own pushes. It then checks the public URL on your custom domain and only counts a post once the page is live. The Jekyll integration page shows the file shape it writes; Next.js, Astro, Hugo, Gatsby and Docusaurus are supported too.
You get 3 posts free, then plans start at $19/month. See pricing and plans.