Field notes

GitHub Pages, custom domain: "Enforce HTTPS" greyed out, certificate stuck, and the www CNAME that causes it

My portfolio sat for weeks with "Enforce HTTPS" greyed out while http:// served the whole site with no redirect. The cause was one DNS record; the diagnosis needed the API rather than the settings page. Here is the checklist I wish I had had.

GitHub PagesHTTPSDNSLet's EncryptStatic sites

The symptom

Settings → Pages shows your custom domain with a green check, but Enforce HTTPS is greyed out. Sometimes with "certificate is being provisioned", sometimes with nothing. Meanwhile:

$ curl -sI http://example.me/ | head -1
HTTP/1.1 200 OK          # served in clear, no redirect
$ curl -sI https://example.me/ | head -1
                         # TLS error, or a certificate for the wrong name

Read the real state from the API

The settings page hides the interesting field. The Pages API does not:

gh api repos/OWNER/REPO/pages --jq '{status, cname, https_enforced, cert: .https_certificate}'

https_certificate.state is what you want. The values you will meet:

state meaning
new / authorization_created GitHub has asked Let's Encrypt; DNS must already be right
authorization_pending the ACME challenge is waiting — usually DNS not propagated
authorized / issued certificate exists; the checkbox becomes clickable within an hour
dns_changed GitHub saw your DNS change and restarted from scratch
errored check description — CAA record, or a record pointing elsewhere

dns_changed is the one that looped for me: every time a record changed, the process restarted, and one record kept "changing".

The DNS that actually works

GitHub's documented setup is short, and every deviation costs a certificate cycle:

example.me.        A      185.199.108.153
example.me.        A      185.199.109.153
example.me.        A      185.199.110.153
example.me.        A      185.199.111.153
www.example.me.    CNAME  OWNER.github.io.      # NOT example.me, NOT an A record

My mistake: www was a CNAME to the apex (example.me) instead of OWNER.github.io. Both resolve to the same addresses, so the site worked on www — but GitHub's certificate request lists both names, its verifier treats a www that does not point at OWNER.github.io as "changed", and the request restarts. The certificate that finally got issued after the fix covers both names:

$ echo | openssl s_client -connect example.me:443 -servername example.me 2>/dev/null \
  | openssl x509 -noout -ext subjectAltName
X509v3 Subject Alternative Name:
    DNS:example.me, DNS:www.example.me

Two more records to check:

Verify from outside your own resolver, because your cache will lie to you for the whole TTL:

dig +short example.me A @8.8.8.8
dig +short www.example.me CNAME @8.8.8.8
dig +short example.me CAA @8.8.8.8

Unsticking a certificate that is "being provisioned" forever

If DNS is right and the state has been authorization_pending for more than a day, GitHub's job is stuck, not yours. What worked for me: in Settings → Pages, remove the custom domain, save, add it back, save. That discards the stuck request and starts a fresh one; with correct DNS the state went to issued within an hour. (Doing the same through the API did not help — the UI path seems to reset more.)

Then wait for the checkbox. It becomes clickable shortly after issued, not at the same moment. Tick it, and within about five minutes:

$ curl -sI http://example.me/ | grep -i '^location\|^HTTP'
HTTP/1.1 301 Moved Permanently
location: https://example.me/

Check the whole matrix, not just the apex: http:// and https:// for the apex, www, and OWNER.github.io, plus any sub-pages. The OWNER.github.io redirect in particular kept pointing at http:// until the checkbox was on.

What the checkbox does not give you

echo | openssl s_client -connect example.me:443 -servername example.me 2>/dev/null \
  | openssl x509 -noout -enddate

Bonus: "Page build failed" with no reason

Unrelated to HTTPS but found the same week: the legacy Pages build failed five times in a row on a commit that added three PNG icons generated with Python. Valid PNGs, no Jekyll, .nojekyll present. Removing the three files fixed it; the same generator's apple-touch-icon.png built fine. I never found the rule — but I did find that a build can also fail transiently for no reason at all, so before bisecting your commit, re-trigger the build and see if it goes green:

gh api -X POST repos/OWNER/REPO/pages/builds
gh api repos/OWNER/REPO/pages/builds/latest --jq '{status, error: .error.message}'

Keep reading