Static Hosting · $0 · Forever

GitHub Pages.
Your site on the real internet, for free, in ten minutes.

GitHub Pages turns a repository into a website: every push to your repo automatically redeploys the site to a global CDN, with free HTTPS, at username.github.io — or your own domain. No servers, no billing page, no credit card field anywhere in the flow. If you finished the Git field guide, you already know 90% of what this takes; the last 10% is a handful of clicks in GitHub's Settings.

git push

your Mac

Pages build

GitHub Actions

Live on CDN

github.io

waiting for push…
Step 01

What "completely free" actually includes

GitHub Pages is genuinely free — not trial-free, not freemium-free. On GitHub's Free plan you get, per account:

  • Hosting for unlimited public repositories, each of which can be a website. (Pages on private repos requires a paid plan — so keep the site's repo public, which is normal for a personal site anyway.)
  • A free subdomain: username.github.io, plus username.github.io/repo-name for every additional project.
  • Free HTTPS — automatically provisioned certificates, on the github.io address and on custom domains alike.
  • A global CDN (Fastly) serving your files from edge locations worldwide.
  • Automatic redeployment on every push — the pipeline animated above.

The fine-print limits are generous for any personal or project site: sites should stay under 1 GB, individual files under 100 MB, bandwidth has a soft limit of 100 GB/month, and builds are capped at 10 per hour. The one hard rule that matters: Pages serves static content only — HTML, CSS, JavaScript, images, fonts. No server-side code runs there: no PHP, no databases, and — relevant to your roadmap — no Perl CGI. Your Apache setup at mysite.test remains the right home for the CGI experiments; Pages is the right home for the static site the world sees.

IDEA

"Static" is less limiting than it sounds. JavaScript still runs in the visitor's browser, so interactive pages — like the HTML guides you build, with their canvas demos and IntersectionObserver navigation — work perfectly. Only server-side execution is off the table.

Step 02

One decision first: user site or project site

Pages offers two flavors, and the only difference in setup is the repository's name:

User siteProject site
Repo must be namedusername.github.io exactlyanything — mysite, french-grammar, …
Site URLhttps://username.github.io/https://username.github.io/mysite/
How many allowedone per accountunlimited
Best foryour main homepage / portfolioeach project, guide collection, experiment

They can coexist: your user site at the root, and any number of project sites hanging off it. Not sure which you're making? Answer below and the rest of the guide's placeholders resolve themselves:

Planner · Fill in your details

interactive
Step 03

Path A — entirely in the browser (no Git needed)

The fastest possible route, good for a first taste or for someone without a terminal handy. Everything happens on github.com:

  1. Create the account

    Sign up at github.com — the free plan is the default; you're never asked for payment. Choose the username carefully: it becomes your site's address, username.github.io, forever visible in the URL bar.

  2. Create the repository

    Click the + in the top-right → New repository. For your homepage, name it exactly username.github.io (substituting your real username — GitHub even hints when you get this right). Set it to Public, tick "Add a README file" so the repo isn't empty, and click Create repository.

  3. Add your index.html

    In the repo, click Add file → Create new file. Name it index.html — this exact name is what Pages serves when someone visits your root URL. Paste your HTML (a minimal starter is below), then click Commit changes. You just made a Git commit without touching Git.

  4. Turn on Pages

    Covered in full in Step 05 — it's the same two clicks for both paths.

index.html · a respectable minimum
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hello from GitHub Pages</title>
</head>
<body>
  <h1>It's alive.</h1>
  <p>Served free, over HTTPS, from a global CDN.</p>
</body>
</html>

You can keep working this way indefinitely — GitHub's web editor (press . in any repo for a full VS Code in the browser) is surprisingly capable. But the moment you have a real project locally, Path B is better in every way.

Step 04

Path B — push your existing site from the Mac

This is the grown-up route, and it's exactly the remote workflow from the Git guide. Suppose the site lives in ~/Sites/mysite and is already a Git repository (if not: git init, git add ., git commit -m "Initial commit" — thirty seconds).

  1. Authenticate once

    GitHub doesn't accept account passwords from Git. The two clean options — pick one:

    option 1 · ssh key (classic, no extra tools)
    ssh-keygen -t ed25519 -C "you@example.com"   # accept defaults
    pbcopy < ~/.ssh/id_ed25519.pub                # copies the PUBLIC key
    # github.com → Settings → SSH and GPG keys → New SSH key → paste
    ssh -T git@github.com                         # test: "Hi username!"
    option 2 · github cli (easiest)
    brew install gh
    gh auth login          # choose GitHub.com → HTTPS → login via browser
  2. Create the empty repo on GitHub

    On github.com: + → New repository, named username.github.io (user site) or e.g. mysite (project site). Public. This time add nothing — no README, no .gitignore — so the remote starts empty and your local history pushes in cleanly. (With the CLI it's one line: gh repo create username.github.io --public --source=. --push does this step and the next two.)

  3. Connect local ↔ remote

    in ~/Sites/mysite
    git remote add origin git@github.com:USERNAME/USERNAME.github.io.git
    git remote -v        # verify — shows fetch & push URLs
  4. Push

    first push
    git push -u origin main
    Enumerating objects: 12, done.
    Writing objects: 100% (12/12), 24.3 KiB | 8.1 MiB/s, done.
    To github.com:USERNAME/USERNAME.github.io.git
     * [new branch]      main -> main

    Refresh the repo page on GitHub — your files are there, full history intact. One step remains.

TIP

Make sure index.html sits at the repository root (or in a /docs folder — see the next step), not nested in some subfolder like mysite/site/html/. Pages serves the chosen folder as-is: root of folder = root of website.

Step 05

Turning Pages on: the two clicks that matter

This is the entire "hosting setup." In your repository on github.com:

RepoSettingsPages (left sidebar, under "Code and automation")
  1. Source: "Deploy from a branch"

    Under Build and deployment → Source, keep Deploy from a branch. (The alternative, GitHub Actions, is for sites that need a build step — Jekyll with plugins, Hugo, a bundler. Plain HTML/CSS/JS doesn't.)

  2. Branch: main, folder: / (root)

    Select branch main and folder / (root), then click Save. The folder dropdown also offers /docs — useful when you want the website in a subfolder of a larger repo (code at root, site in docs/).

  3. That's it — wait ~1 minute

    GitHub kicks off the first deploy immediately. Refresh the Pages settings page and a banner appears: "Your site is live at https://username.github.io/" with a Visit site button. First deploys can take a couple of minutes; subsequent ones are usually 20–60 seconds.

CARE

404 after it says "live"? Ninety percent of the time it's one of two things: the file isn't named exactly index.html (lowercase, no Index.html or index.HTML), or it isn't at the root of the folder you selected. The other ten percent: give the CDN two more minutes and hard-refresh (⌘⇧R).

Step 06

Watching the deploy (and knowing when it's done)

Every deploy — including that first one — runs as a visible workflow. Two places to watch:

  • The Actions tab of your repo lists a workflow called pages-build-deployment for every push: amber dot = running, green check = live, red X = failed (click it for logs).
  • The repo homepage shows the same status as a tiny icon next to the latest commit's hash.
or never leave the terminal (github cli)
gh run list --limit 3
STATUS  NAME                     BRANCH  EVENT   
✓       pages build and deploy…  main    dynamic 

gh run watch          # live-follows the current deploy to completion
gh browse             # opens the repo; add --settings for Settings

When the check turns green, the site is live at your URL. Verify the essentials once: pages load over https://, internal links work, images appear. Then check Settings → Pages and tick "Enforce HTTPS" if it isn't already (it usually is) — this redirects any http:// visitor to the secure version.

Step 07

Updating the site: the loop you already know

From now on, "deploying" is not a separate skill. It's the Git loop with one extra word:

the entire maintenance workflow, forever
# edit files locally, preview in the browser, then:
git add .
git commit -m "Add Spanish grammar chart page"
git push
# ~30 seconds later the live site reflects it. That's the whole deploy.

# with the aliases from the Git guide, a full publish is:
git aa && git cm "Update charts" && git pu

Notice what you get free that FTP-era hosting never gave you: every version of the live site ever published is a commit you can inspect, diff, and — if a deploy breaks something — instantly roll back:

rolling back a bad deploy
git revert HEAD        # new commit that undoes the last one
git push               # site redeploys to the previous state
IDEA

Local preview stays local. Your Apache at mysite.test is now the staging server: preview there, and only git push when it looks right. Two hosts, one folder, zero conflict.

Step 08

Optional: your own domain (still free on GitHub's side)

The only money in this entire guide is optional: a domain name (~$10–15/year from a registrar like Cloudflare, Porkbun, or Namecheap). GitHub's side — hosting, HTTPS certificate for the domain — costs nothing. Two halves to configure:

Half 1 · at your DNS provider

Create these records (the four A records are GitHub's permanent Pages addresses):

TypeName / HostValue
A@ (apex, i.e. example.com)185.199.108.153
A@185.199.109.153
A@185.199.110.153
A@185.199.111.153
CNAMEwwwusername.github.io

Half 2 · in GitHub

  1. Enter the domain

    Settings → Pages → Custom domain: type www.example.com (or the apex example.com), click Save. GitHub adds a CNAME file to your repo and starts a DNS check.

  2. Wait for the check, then enforce HTTPS

    DNS propagation takes minutes to a few hours. When the check shows green, tick Enforce HTTPS. GitHub provisions a Let's Encrypt certificate for your domain automatically — the checkbox may be greyed for up to a day while that happens; it resolves on its own.

  3. (Recommended) verify the domain account-wide

    Account Settings → Pages → Add a domain lets you verify ownership via a TXT record, which prevents anyone from claiming your domain on Pages if you ever unlink it.

Or generate your exact records here:

Generator · DNS records for your domain

interactive
Step 09

Polish & the gotchas worth knowing

The project-site path gotcha (the #1 trap)

A project site lives at username.github.io/mysite/ — note the subpath. Any link or asset URL starting with / points at the domain root, above your site, and breaks:

absolute vs relative paths on a project site
<!-- BREAKS on a project site: resolves to username.github.io/style.css -->
<link rel="stylesheet" href="/style.css">

<!-- WORKS everywhere: relative to the current page -->
<link rel="stylesheet" href="style.css">
<a href="charts/french.html">French charts</a>
<img src="img/hero.png">

User sites (username.github.io) sit at the domain root, so both styles work there — but relative paths are the habit that never bites.

A custom 404 page

Drop a 404.html at the repo root and Pages serves it for any missing URL — same design language as your site instead of GitHub's default.

The .nojekyll file

By default, Pages runs your files through Jekyll, a static-site generator. For plain HTML it's nearly transparent — except it silently skips any file or folder starting with an underscore (_drafts/, _data.js). Opt out with an empty marker file and Pages serves everything verbatim, slightly faster:

recommended for hand-written sites
touch .nojekyll
git add .nojekyll && git commit -m "Serve files verbatim" && git push

Things Pages will never do (plan around them)

  • No server-side code — for form handling use a service (Formspree et al.) or a separate backend; for Perl CGI, that's your Apache box.
  • No secrets in the repo — the repository is public; so is every file and its entire history.
  • Not for commercial storefronts — GitHub's terms disallow primarily-commercial/e-commerce use; personal sites, docs, portfolios, and project pages are exactly the intended use.
Step 10

Troubleshooting

SymptomLikely causeFix
404 at the site URLno index.html at the published folder's root, or wrong caserename to exactly index.html; confirm branch/folder in Settings → Pages
Site loads, CSS/images missingabsolute /paths on a project site; or case mismatch (Hero.PNG vs hero.png — Pages is case-sensitive, macOS isn't)switch to relative paths; match filename case exactly
Changes pushed but site unchangeddeploy still running, or browser cachecheck Actions tab for green ✓; hard-refresh ⌘⇧R
Push rejected: fetch firstremote has commits you don't (e.g. README made on the site)git pull --rebase, then push
Files with _underscore names vanishJekyll processingadd .nojekyll (previous section)
Custom domain check stuck / redDNS not propagated or records wrongverify with dig example.com +short → should list the four 185.199.* IPs; wait, then re-save the domain
"Enforce HTTPS" greyed outcertificate still provisioningnormal for up to ~24 h after DNS goes green; check back
Red X on the deploy runbuild error (usually Jekyll choking on a file)click the run in Actions for the log; .nojekyll often cures it
Step 11

Launch checklist

Tick as you go — progress is saved only in this browser session.

0 / 10 complete

From here, publishing is a reflex: git aa && git cm "…" && git pu — and thirty seconds later, the world sees it.