A safe harbor for coders and developers

Tutorials

How to Deploy a Static Website with GitHub Pages and a Custom Domain

How to Deploy a Static Website with GitHub Pages and a Custom Domain

There is a particular pleasure in pushing a folder of HTML to GitHub and watching it appear on the open web a minute later. GitHub Pages does exactly that: it serves static files straight out of a repository, over HTTPS, with no server to patch and no monthly bill. The only awkward part is the default address — yourusername.github.io — which is fine for a weekend experiment and less fine for a portfolio you actually send to people. Getting from a local folder to a live custom domain takes about twenty minutes of work and a variable amount of waiting on DNS.

What GitHub Pages handles well, and what it does not

Pages serves static assets: HTML, CSS, JavaScript, images, fonts, PDFs. That covers portfolios, documentation sites, blogs, landing pages, and project demos. It does not run server-side code. There is no PHP, no Node process waiting for requests, no database you can query at render time. If you need form submissions, search, or authentication, you bolt on a third-party service or move to a platform built for it.

Static site generators fit naturally. Jekyll is processed automatically by GitHub, and if you prefer Hugo, Eleventy, Astro, or Next.js in static export mode, you can build the site in a GitHub Actions workflow and publish the output folder. The free tier is generous for personal projects: a soft repository size limit around 1 GB, roughly 100 GB of bandwidth per month, and about ten builds per hour. Hobby traffic will not trouble any of those numbers.

Step 1: Put the site in a repository

Create a new repository and choose the name with some care, because it decides your default URL. A repository named yourusername.github.io becomes your root site, served at that address. Any other name — portfolio, recipe-blog, docs — becomes a project site at yourusername.github.io/portfolio. Both work with a custom domain later; the difference is only how the fallback URL looks and how many sites share the domain.

Push your files with git, or drag them into the browser uploader if you are starting small. You need an index.html at the top level of whatever folder you publish from. If your build tool outputs directories beginning with an underscore, add an empty file called .nojekyll to the root so GitHub skips its Jekyll processing and serves your files untouched.

One habit worth forming early: file names are case-sensitive on GitHub's servers even if your laptop ignores case. Images/Hero.PNG and images/hero.png are different files. Mismatched case is the single most common cause of a site that looks perfect locally and broken once deployed.

Step 2: Publish from the repository settings

Open the repository, go to Settings, then Pages in the sidebar. Under "Build and deployment", set the source to Deploy from a branch, pick your branch (usually main), and choose the folder — / (root) or /docs if you keep the built site in a docs folder. Save, and wait a minute or two. The Pages panel shows the live URL once the first build finishes, and the Actions tab shows progress if something goes wrong.

If you would rather build the site on every push, switch the source to GitHub Actions and pick a starter workflow for your generator. That route gives you a real build step, dependency installation, and logs you can read — handy when a template breaks.

Step 3: Connect your custom domain

You keep your domain at whichever registrar you bought it from. You do not need to transfer it. You only need to add DNS records that point at GitHub, and there are two shapes depending on whether you are using an apex domain or a subdomain.

Point DNS at GitHub Pages

  • Apex domain (example.com): create four A records pointing to GitHub's published addresses — 185.199.108.153, 185.199.109.153, 185.199.110.153 and 185.199.111.153. Add matching AAAA records for IPv6 if your registrar supports them. GitHub lists the current values in its Pages documentation, so check there before typing them in; addresses can change.
  • Subdomain (www.example.com, blog.example.com): create a single CNAME record pointing to yourusername.github.io. No trailing slash, no path.
  • Remove conflicts. Delete old A records pointing at a previous host, and avoid wildcard records that swallow subdomains you might want later.

DNS changes usually settle within an hour, though registrars sometimes take longer to propagate. One useful detail: GitHub Pages automatically redirects between the apex and www versions once both resolve, so visitors land on one canonical address either way.

Tell GitHub the domain

Back in Settings → Pages, type your domain into the Custom domain field and save. GitHub writes a small file called CNAME into the root of your publishing branch. Do not delete it, and do not let a build step wipe it out — it is what tells Pages which hostname to answer for. After saving, the DNS check should pass within a few minutes. If it does not, give propagation more time before changing anything.

Step 4: Turn on HTTPS

With the domain verified, GitHub requests a free certificate automatically. The Enforce HTTPS checkbox stays greyed out until that certificate exists, then becomes clickable. Enable it, and every request to the plain HTTP version redirects to the secure one.

Two things can block this. A CDN sitting in front of your DNS in proxy mode may prevent GitHub from completing its validation — switch that record to DNS-only temporarily, then re-enable the proxy once the certificate is issued. And a CAA record at your registrar that names only one certificate authority can stop the issuance entirely; make sure it allows the authorities GitHub uses, or remove it. Also check that your pages load assets over HTTPS. A stylesheet or font pulled from an http:// URL triggers mixed-content warnings and breaks the lock icon.

Troubleshooting the usual suspects

  • 404 on the custom domain, works on github.io: almost always DNS. Verify the A records or CNAME with a lookup tool rather than assuming they saved.
  • 404 everywhere: the publishing branch or folder is wrong, or there is no index.html where you pointed it.
  • Unstyled page: assets referenced with absolute paths that still point at the old github.io address, or a filename case mismatch.
  • Certificate never issues: a proxied DNS record or a restrictive CAA entry, as above.
  • Changes not showing: build still running, browser cache, or a service worker holding an old version. Check the Actions tab first.

A custom 404.html in your published folder is picked up automatically, which is a cheap way to keep broken links on-brand.

Keeping it running without thinking about it

Save the DNS records somewhere you will find them in two years — a note in the repository README is fine. Turn on auto-renew at your registrar so the domain does not quietly expire and take the site with it. Keep the CNAME file in version control rather than letting it live only in the deployed output.

And remember that anything published through GitHub Pages is public, even when the source repository is not. Never commit API keys, tokens, or .env files. If a secret does slip in, rotate it immediately — deleting the commit is not enough, because the history and the cache may still hold it. Set it up once, and the site simply sits there, fast and free, while you get on with writing code.

Photo: Brett Sayles / Pexels

Related Posts

Top 10 VS Code Extensions for Python Developers in 2025

Tools

Top 10 VS Code Extensions for Python Developers in 2025

Ten VS Code extensions that genuinely help Python work in 2025, with honest pros, cons, and the order to install them in.

Code Review Checklist: 15 Things to Check Before Merging a Pull Request

Tools

Code Review Checklist: 15 Things to Check Before Merging a Pull Request

A practical 15-point code review checklist covering scope, readability, security, performance, tests, and...