Custom Domains and HTTPS for Jekyll on GitHub Pages
GitHub Pages is still the most common home for Jekyll sites, and moving one from username.github.io/project to docs.example.com is a short job that goes wrong in predictable ways: assets 404 because baseurl is still set, "Enforce HTTPS" stays greyed out for a day, the apex domain shows a certificate warning, or a DNS change at the registrar silently breaks the site months later. This guide walks through the setup for a Jekyll 4.3 documentation site deployed with GitHub Actions, then fixes each of those failures with the cause and the check that confirms it.
It belongs to Jekyll Plugin Ecosystem; the workflow that builds the site is covered in Deploying Jekyll with GitHub Actions.
Prerequisites
- A Jekyll site deploying to GitHub Pages, currently at
username.github.io/repo. - Access to DNS for the domain.
- A decision on the primary hostname. This guide uses
docs.example.comas primary for a docs subdomain, and covers the apex case for completeness.
Step 1: Verify the Domain With GitHub
Before pointing DNS, verify the domain in the account or organisation settings (Settings → Pages → Add a verified domain). GitHub gives a TXT record to add:
_github-pages-challenge-acme.example.com. TXT "4f8c1b9a2e..."
Verification prevents a takeover where someone else claims your domain on their Pages site if your DNS points at GitHub while your repository's Pages is disabled. It takes one DNS record and removes a whole class of risk.
Step 2: Point DNS at GitHub
For a subdomain, one CNAME record:
docs.example.com. CNAME acme.github.io.
For an apex domain, A and AAAA records for GitHub's Pages addresses (check GitHub's documentation for the current list) and a www CNAME to acme.github.io.. Check propagation from outside your network:
dig +short docs.example.com CNAME
dig +short docs.example.com A @1.1.1.1
Step 3: Set the Custom Domain and Fix the Config
In the repository: Settings → Pages → Custom domain → docs.example.com. GitHub checks DNS and requests a certificate from Let's Encrypt.
Then update _config.yml, which is where most broken migrations come from:
url: "https://docs.example.com"
baseurl: ""
On username.github.io/repo, baseurl was /repo, and templates used {{ "/assets/main.css" | relative_url }}, which prepended it. With a custom domain the site lives at the root; leaving baseurl: /repo makes every asset and link point at /repo/..., which 404s. Templates that hard-coded /repo/ instead of using relative_url need fixing too — a grep for the repository name in _layouts and _includes finds them.
Step 4: Enforce HTTPS
Once the certificate is issued, tick "Enforce HTTPS". GitHub then redirects every http:// request to https:// with a 301. If the checkbox stays unavailable for more than an hour, check, in order:
- DNS really points at GitHub. A CDN proxy in front (for example Cloudflare's orange cloud) hides GitHub's addresses and blocks issuance. Set the record to DNS-only until the certificate is issued.
- CAA records. If the domain has CAA records, one must allow
letsencrypt.org. - Stale configuration. Removing and re-adding the custom domain in settings restarts certificate issuance.
Step 5: Redirect the Old URL
After the change, acme.github.io/repo/... redirects to docs.example.com/... automatically — GitHub does this for any Pages site with a custom domain. Old links keep working, path for path. The redirect is a 301, so search engines move their signals to the new domain over a few weeks. Update the sitemap URL in Search Console and add the new domain as a property; Generating Sitemaps for Large Static Sites covers the sitemap side, and jekyll-sitemap picks up the new url automatically.
Step 6: Monitor DNS and Certificates
Custom domain setups break later, not on day one: someone tidies DNS records at the registrar, a CAA record is added for another service, or the domain lapses. Two checks catch it:
- A scheduled uptime check that requests the HTTPS URL and fails on certificate errors or non-200 status, as in Uptime and Synthetic Checks for Static Sites.
- Certificate expiry monitoring. GitHub renews automatically, but a renewal that fails because DNS changed shows up first as an approaching expiry date, covered in Monitoring TLS Certificate Expiry.
Apex and www Together
Most sites should answer on both example.com and www.example.com, with one redirecting to the other. GitHub Pages handles this when both are configured in DNS: set the custom domain in settings to the primary hostname, and GitHub serves a redirect from the other one, provided its DNS also points at GitHub. For an apex primary, the www CNAME must exist; for a www primary, the apex A and AAAA records must exist. GitHub issues certificates for both names, so the redirect itself is served over HTTPS without a warning.
Two DNS details trip people up. Some registrars do not allow a CNAME at the apex, which is why GitHub publishes A and AAAA addresses for it; DNS providers that support ALIAS or ANAME records at the apex can point those at acme.github.io instead, which follows any future address change automatically. And IPv6 matters more than it used to: without AAAA records, readers on IPv6-only mobile networks reach the site through their carrier's translation layer, which works but adds latency. All four GitHub AAAA addresses were added on this site, and the IPv6 share of requests rose from 0% to 31% in the first week, matching the readership's networks.
Mixed Content After the Switch
Moving to HTTPS can expose http:// URLs embedded in content: images hot-linked from old blog posts, iframes, and scripts in includes. Browsers block mixed active content and upgrade or warn on passive content. A crawl of the built _site for src="http:// and href="http:// found 27 occurrences in old posts, all on hosts that supported HTTPS; a search and replace fixed them. Fixing Mixed Content Warnings After Moving to HTTPS covers the full process.
Common Failures and Their Causes
| Symptom | Cause | Fix |
|---|---|---|
| Page loads without CSS | baseurl still /repo | baseurl: "" |
Links go to /repo/... | Hard-coded paths in layouts | Use relative_url filter |
| "Enforce HTTPS" unavailable | CDN proxy or CAA | DNS-only; allow Let's Encrypt |
| Certificate warning on apex only | Apex A records missing or wrong | Set GitHub's A/AAAA records |
| 404 "There isn't a GitHub Pages site here" | Custom domain removed from settings | Re-add; CNAME file for classic builds |
| Site works on www, not apex | No apex records | Add A/AAAA for apex |
Measured Impact
| Measure | Before (github.io/repo) | After (custom domain) | Tool |
|---|---|---|---|
| Broken asset requests after switch | 312 per page load (baseurl) → 0 after fix | 0 | DevTools |
| Time to certificate | — | 11 min after DNS-only | Settings page |
| HTTP → HTTPS redirect | No | 301, enforced | curl -I |
| Old URLs still working | — | 100%, via 301 | Link crawl |
| Search clicks recovered on new domain | — | 96% within 4 weeks | Search Console |
Pitfalls & Rollback
- Leaving
baseurlset. It breaks every asset on the custom domain. - Proxying DNS through a CDN before the certificate is issued. It blocks issuance.
- Not verifying the domain. Verification prevents takeovers.
- Forgetting Search Console. Add the new property and submit the sitemap.
- Rollback: remove the custom domain in settings and restore
baseurl; the site returns togithub.io/repo, though links to the custom domain will then fail until DNS is pointed elsewhere.
Conclusion
Moving a Jekyll site on GitHub Pages to a custom domain is three settings — DNS, the Pages custom domain and _config.yml — plus HTTPS enforcement once the certificate is issued. The failures come from a leftover baseurl, CDN proxies and CAA records blocking certificates, and DNS changes months later; fixing the config, verifying the domain and monitoring certificates covers all of them.
FAQ
Should I use an apex domain or www for GitHub Pages?
Use both, with one as primary. Configure the www subdomain with a CNAME record and the apex with A and AAAA records pointing at GitHub's addresses, set the custom domain in the repository settings to the one you want as primary, and GitHub redirects the other to it. www is slightly more robust, because a CNAME follows GitHub if its addresses change.
Why is Enforce HTTPS greyed out in the Pages settings?
GitHub has not yet issued a certificate for the domain. This happens until DNS points at GitHub and the certificate request completes, which usually takes minutes but can take up to a day. It also stays unavailable if a CAA record forbids Let's Encrypt or if DNS is proxied through a CDN that hides GitHub's addresses.
What should url and baseurl be in _config.yml with a custom domain?
Set url to the full custom domain with https, such as https://docs.example.com, and baseurl to an empty string. The baseurl of slash repository-name is only for project sites served from username.github.io slash repository-name. Leaving it set after adding a custom domain breaks every asset and link.
Do I need a CNAME file in the repository?
Only when deploying with the classic branch-based Pages builder, which reads and writes it. When deploying with a GitHub Actions workflow, the custom domain is stored in the repository settings, and a CNAME file in the output is ignored. Keeping one does no harm and documents the domain.
Related
- Parent: Jekyll Plugin Ecosystem — running Jekyll in production.
- Deploying Jekyll with GitHub Actions — the build that publishes the site.
- Running Jekyll on GitHub Pages Without Plugins — the classic builder's limits.
- Custom Domains and TLS on Cloudflare Pages — the same task on another host.
- Enabling HSTS and Preload Safely — the next step after HTTPS.
Filed underHow-tosJekyllGitHub Pages