Skip to content

Pages enablement runbook

This runbook drives apothem.ahmedgad.com from a fresh GitHub Pages configuration to a 200 OK HTTPS landing page. The procedure is written for the operator wielding gh and a DNS-provider console; a competent new contributor follows it without prior context.

The runbook is structured as a provider-agnostic core (the GitHub-side and DNS-record steps every provider shares) followed by provider-specific appendices for Namecheap, Cloudflare, Route53, and a generic-DNS fallback. Run the core in order; jump to the appendix matching the apex domain registrar / DNS host; return to the core for verification.

The site is hosted from a private repository through GitHub Pages with the custom-domain CNAME pointing to gad360.github.io. The MkDocs build emits to site/; the Pages workflow lifts the build artifact onto the live site at every push to main.

1. Prerequisites

The operator confirms the following before proceeding:

  • Repository state. Gad360/apothem is private, has Pages enabled (Settings > Pages > Source: GitHub Actions), and the publish-static-site.yml workflow exists at .github/workflows/publish-static-site.yml.
  • DNS authority. The operator controls DNS for ahmedgad.com at the registrar or at a delegated DNS host (Cloudflare, Route53, etc.). The DNS host is identified via dig NS ahmedgad.com +short.
  • Tooling. gh (GitHub CLI) version 2.40 or later authenticated against the Gad360 account; dig (BIND dig or drill) for DNS verification; a browser for HTTPS inspection.
  • Apex stance. This runbook configures the subdomain apothem.ahmedgad.com only. The apex ahmedgad.com is untouched. CNAME at apex requires CNAME-flattening (Cloudflare) or ALIAS records (Route53); the subdomain path avoids both classes of complication.

2. Provider-agnostic core

2.1 Add the custom domain to Pages

gh api -X PUT \
  /repos/Gad360/apothem/pages \
  -F cname=apothem.ahmedgad.com \
  -F https_enforced=true

Verify the call returned the new CNAME record:

gh api /repos/Gad360/apothem/pages \
  --jq '.cname, .https_enforced, .status'

Expected output:

apothem.ahmedgad.com
true
built

The built status confirms the most recent Pages build succeeded. A null status means Pages has not run since the CNAME was applied — push a no-op commit to main to trigger the build.

2.2 Confirm the CNAME file

GitHub Pages inspects docs/CNAME (or the repository root's CNAME, depending on Pages source configuration) on every build. The file contains the custom domain on a single line, no scheme, no trailing slash:

apothem.ahmedgad.com

The file lives at docs/CNAME because the docs build emits from the docs/ source tree. Verify:

cat docs/CNAME

If the file is missing, recreate it at docs/CNAME and commit under chore: restore docs/CNAME for Pages custom domain.

2.3 Add the DNS record

Pages serves the custom domain when the apex DNS provider returns one of GitHub's four IP addresses (for apex domains) or a CNAME pointing to <owner>.github.io (for subdomains). For apothem.ahmedgad.com the record class is CNAME:

Field Value
Type CNAME
Host apothem (subdomain only; the registrar appends the apex)
Value gad360.github.io. (trailing dot when the registrar requires it)
TTL 3600 (one hour; lower for faster propagation during cutover)

The exact UI flow for adding the record varies per provider; jump to the matching appendix below, return here when the record is saved.

2.4 Wait for DNS propagation

Propagation completes within five minutes for low-TTL records and within an hour for the default 3600-second TTL — provider edges may extend the window during a DNS-zone transfer. Track progress:

dig apothem.ahmedgad.com CNAME +short

Expected output:

gad360.github.io.

Empty output means the record has not propagated yet. Re-run every 30 seconds. If the record never appears after 30 minutes, return to the appendix matching the DNS host and verify the record is committed at the provider, not pending in a draft state.

2.5 Verify HTTPS

curl -sSI https://apothem.ahmedgad.com/ | head -5

Expected output:

HTTP/2 200
server: GitHub.com
content-type: text/html; charset=utf-8
strict-transport-security: max-age=31536000

HTTP/2 200 and the server: GitHub.com line together confirm Pages is serving the site under a valid TLS certificate. A 301 to the HTTPS URL on a plain-HTTP request also satisfies the gate; HTTPS-enforced is on.

If curl returns a 526 (Cloudflare-specific invalid-certificate code) or 502 (bad gateway), the certificate has not yet been issued. Pages provisions Let's Encrypt certificates automatically once the CNAME resolves; allow up to 24 hours for first issuance. For diagnostics:

gh api /repos/Gad360/apothem/pages \
  --jq '.https_certificate.state, .https_certificate.description'

state: approved means issuance is complete. state: requesting means issuance is in progress; wait. Any other state requires a Pages restart (toggle the custom domain off and on via the API).

curl -sSI https://apothem.ahmedgad.com/architecture/ | head -3

Expected output:

HTTP/2 200
server: GitHub.com
content-type: text/html; charset=utf-8

A 404 on a deep link means the MkDocs nav does not include the page or the build has not picked up the change. Trigger a rebuild via gh workflow run publish-static-site.yml.

2.7 Record the verification

Append a row to the operator's site-status log (or to this runbook's verification log if maintained inline) with the date, the operator's identity, and the gh api Pages-status output. The verification log is the audit trail — every Pages-config change leaves a row.

3. Provider appendix — Namecheap

Namecheap exposes DNS through the registrar's "Advanced DNS" tab. The operator owns ahmedgad.com here when the registrar's NS records match dns1.registrar-servers.com and dns2.registrar-servers.com (the Namecheap default). Verify with dig NS ahmedgad.com +short.

Steps:

  1. Sign in to Namecheap; open the dashboard.
  2. Click Domain List in the left navigation; locate ahmedgad.com; click Manage.
  3. Click the Advanced DNS tab.
  4. Click Add New Record. Choose:
  5. Type: CNAME Record
  6. Host: apothem (no apex, no trailing dot)
  7. Value: gad360.github.io. (with trailing dot)
  8. TTL: Automatic (Namecheap default; honours 1800 seconds)
  9. Click the green checkmark to save.
  10. Wait 5 to 10 minutes for Namecheap's edge propagation; then return to the provider-agnostic core §2.4 for dig verification.

Common failure: Namecheap's UI silently rejects CNAME records when an A record already occupies the same host. Delete the conflicting A record first; CNAME and A on the same host are mutually exclusive per RFC 1034.

4. Provider appendix — Cloudflare

Cloudflare DNS hosts the apex when the registrar's NS records point to *.ns.cloudflare.com (the Cloudflare default after delegation). Verify with dig NS ahmedgad.com +short.

Steps:

  1. Sign in to Cloudflare; select the ahmedgad.com zone.
  2. Click DNS -> Records in the left navigation.
  3. Click Add record. Choose:
  4. Type: CNAME
  5. Name: apothem (Cloudflare auto-appends the apex)
  6. Target: gad360.github.io (no trailing dot in Cloudflare's UI)
  7. TTL: Auto (Cloudflare's default; routes through its edge)
  8. Proxy status: DNS only (gray cloud, not orange)
  9. Click Save.

The proxy-status choice is load-bearing. Setting Cloudflare's proxy on (orange cloud) routes traffic through Cloudflare's edge before reaching GitHub Pages; this breaks Pages' Let's Encrypt issuance because Cloudflare presents its own certificate. Keep proxy off for the initial setup; turn proxy on later only after deciding whether to manage the certificate at Cloudflare's edge.

  1. Wait 1 to 2 minutes for Cloudflare's edge propagation; then return to the provider-agnostic core §2.4.

Common failure: Cloudflare strips the trailing dot from CNAME targets silently. The target field accepts gad360.github.io without the trailing dot; the resolver appends the trailing dot at query time. Do not type the trailing dot in Cloudflare's UI — it produces a double-dot record that fails to resolve.

5. Provider appendix — Route53

Route53 hosts the zone when the registrar's NS records point to four awsdns-*.com / awsdns-*.org / awsdns-*.co.uk / awsdns-*.net servers. Verify with dig NS ahmedgad.com +short.

Steps:

  1. Open the AWS Console; navigate to Route53; click Hosted zones; select the ahmedgad.com zone.
  2. Click Create record.
  3. Configure:
  4. Record name: apothem (Route53 auto-appends the apex)
  5. Record type: CNAME
  6. Value: gad360.github.io (no trailing dot)
  7. TTL: 300 (5 minutes; Route53's defaults are higher; lower the TTL for faster propagation during cutover)
  8. Routing policy: Simple routing
  9. Click Create records.
  10. Wait 1 to 5 minutes for Route53 propagation; return to §2.4.

Route53 supports alias records that resolve at query time without a CNAME indirection. Alias records are not used here because alias targets are restricted to AWS resources (CloudFront, S3, ELB, etc.) and gad360.github.io is not an AWS resource. CNAME is correct.

Common failure: Route53's UI accepts duplicate record names with different routing policies; this surfaces as a healthcheck-style failover routing rather than a simple CNAME. Confirm Routing policy reads Simple routing before saving.

6. Provider appendix — Generic DNS

For DNS hosts not covered by the named appendices (most registrars' default DNS panels — GoDaddy, Hover, Gandi, OVH, Porkbun, Domains.google, Network Solutions), apply the provider-agnostic CNAME shape:

Field Value
Type CNAME
Host apothem (subdomain only)
Value gad360.github.io. (trailing dot — most generic panels accept it; some require it)
TTL 3600 (or the panel's lowest available value during cutover)

The two failure classes that recur across generic panels:

  • Trailing-dot inconsistency. Some panels require the trailing dot; others reject it; a few accept either. When the record fails to propagate after 30 minutes, toggle the trailing dot and resave.
  • Conflicting A record at the same host. CNAME and A on the same host violate RFC 1034 §3.6.2. Delete the conflicting A record before adding the CNAME.

After the record is saved, return to §2.4 for dig verification.

7. Failure recovery

The Pages-enablement procedure has four documented failure modes:

Failure Diagnostic Recovery
gh api .../pages returns 404 Pages is not enabled on the repo gh api -X POST /repos/Gad360/apothem/pages -f source.branch=main -f source.path=/site then retry §2.1
dig returns empty after 30 minutes DNS record not committed at provider Return to the matching provider appendix; verify the record is saved (not draft / pending); inspect the panel for a Save step missed
https_certificate.state: errored Pages cannot issue a certificate Toggle the custom domain off and on at Pages settings; this re-triggers issuance. Wait up to 24 hours for the new attempt
Deep-link 404 after build MkDocs build did not include the page gh workflow run publish-static-site.yml; on completion verify gh run list --workflow=publish-static-site.yml --limit=1 --json conclusion --jq '.[0].conclusion' returns success

If recovery fails after one cycle of the matching diagnostic, escalate to the release-recovery runbook (force-push fresh history may have left the Pages config in an inconsistent state).

8. Cross-references

  • Spec source. Specification §2.5 lists this runbook in the Cluster 4 documentation deliverables.
  • CNAME canonical location. The CNAME file lives at docs/CNAME and is committed to the repository; Pages reads it on every build.
  • Recovery flow. The release-recovery runbook re-applies Pages configuration from the recovery snapshot when the cutover sequence fails mid-flight.
  • Release cycle. Every release run includes a Pages-build job that verifies the site renders at the custom domain.