Home DevOps GitHub Pages — The Custom Domain That Went to the Wrong Server
Beginner 4 min · July 12, 2026
GitHub Pages: Host Static Sites from Your Repository

GitHub Pages — The Custom Domain That Went to the Wrong Server

A GitHub Pages custom domain pointed to the wrong server after a CNAME file was accidentally deleted.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Production
production tested
July 12, 2026
last updated
2,406
articles · all by Naren
Before you start⏱ 15 min
  • Git installed (v2.30+), GitHub account, basic knowledge of Git (commit, push, branch), familiarity with HTML/CSS, optional: a static site generator (Jekyll, Hugo, etc.) if using one.
 ● Production Incident
Quick Answer

1. Push your site to a repo. 2. Go to Settings → Pages. 3. Select source branch (gh-pages or main) and folder (/root or /docs). 4. (Optional) Add a custom domain via CNAME record. GitHub serves your site automatically.

✦ Definition~90s read
What is GitHub Pages?

GitHub Pages hosts static websites directly from a GitHub repository. Push HTML/CSS/JS to a branch (gh-pages or main in a special repo), and GitHub serves it at <username>.github.io/<repo> or a custom domain.

GitHub Pages is like having a free web host built into every GitHub repo.
Plain-English First

GitHub Pages is like having a free web host built into every GitHub repo. You push your website files to a special branch, and GitHub automatically serves them to visitors. It's like dropping PDFs into a Dropbox folder and getting a URL to share — but for websites. No server setup, no hosting bills, no deployment scripts needed.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

GitHub Pages is the simplest way to host a static site: push code, get a URL. It powers documentation sites, personal portfolios, project landing pages, and even full static blogs (via Jekyll). The simplicity is the killer feature: there's no server to manage, no CI/CD pipeline to configure, no hosting bill. But simplicity has edge cases: the CNAME file, 404 handling, and custom domain DNS propagation. This guide covers setup, the custom domain incident that took down a product launch, and how to avoid the most common Pages pitfalls.

What Is GitHub Pages and Why Use It?

GitHub Pages is a static site hosting service that takes HTML, CSS, and JavaScript files directly from a repository on GitHub, optionally runs the files through a build process, and publishes a website. It's free, integrates with your existing Git workflow, and supports custom domains and HTTPS. For production use, it's ideal for documentation sites, project landing pages, personal portfolios, and even small business sites. The key advantage is zero server management: you push code, and GitHub handles deployment. However, it's not a dynamic hosting platform — no server-side processing, databases, or backend logic. If you need those, look elsewhere. For static content, it's a no-brainer.

.github/workflows/pages.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
name: Deploy to GitHub Pages
on:
  push:
    branches: ["main"]
  workflow_dispatch:
permissions:
  contents: read
  pages: write
  id-token: write
concurrency:
  group: "pages"
  cancel-in-progress: false
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/configure-pages@v5
      - uses: actions/upload-pages-artifact@v3
  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/deploy-pages@v4
Output
Deployment triggered on push to main. Artifact uploaded and deployed.
🔥Static vs Dynamic
GitHub Pages only serves static files. If your site needs server-side rendering, user authentication, or a database, use a platform like Vercel, Netlify, or a VPS.
📊 Production Insight
We once used GitHub Pages for a client's marketing site. The build step failed silently because we forgot to configure the Pages source branch. Always verify the deployment status in the repo's Actions tab.
🎯 Key Takeaway
GitHub Pages is free, Git-integrated static hosting — perfect for docs, portfolios, and landing pages.
github-pages THECODEFORGE.IO GitHub Pages DNS Architecture Layered components for custom domain resolution User Browser DNS Query | HTTPS Request DNS Resolution Registrar Nameservers | A Record | CNAME Record GitHub Infrastructure GitHub Pages Server | Load Balancer | CDN Edge Repository Layer username.github.io Repo | Static Site Files | CNAME File THECODEFORGE.IO
thecodeforge.io
Github Pages

Setting Up Your Repository for GitHub Pages

To host a site, you need a repository. For a user or organization site, name the repo <username>.github.io. For project sites, any name works, and the site will be at <username>.github.io/<repo-name>. Enable Pages in the repo Settings > Pages. Choose the source branch (usually main or gh-pages) and optionally a /docs folder. For modern static site generators (Jekyll, Hugo, etc.), GitHub Pages can build your site automatically. For plain HTML, just push your files. Important: if you use a custom domain, add a CNAME file in the root of your publishing source with your domain (e.g., example.com). Also configure your DNS provider with a CNAME record pointing to <username>.github.io.

terminalBASH
1
2
3
4
5
6
7
8
# Create a new repository for a user site
git init my-site
cd my-site
echo "<h1>Hello World</h1>" > index.html
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/username/username.github.io.git
git push -u origin main
Output
Repository created and pushed. Site will be live at https://username.github.io after a few minutes.
⚠ Branch Naming
If you use a branch other than main or gh-pages, GitHub Pages won't deploy. Stick to the defaults or configure explicitly in Settings.
📊 Production Insight
A common mistake: pushing to main but forgetting to set the Pages source to main. The site stays on the old version. Always double-check the source branch after initial setup.
🎯 Key Takeaway
Name your repo correctly and configure the source branch in Settings to enable Pages.

Using a Static Site Generator with GitHub Pages

GitHub Pages natively supports Jekyll, but you can use any static site generator (Hugo, Next.js, Astro) by building locally and pushing the output, or using GitHub Actions to build and deploy. For Jekyll, just push your source files; GitHub builds them automatically. For others, you need a workflow. The advantage of a generator: templating, Markdown support, asset pipeline, and SEO optimization. For production, I recommend using a CI/CD pipeline to ensure builds are reproducible and tested. Avoid committing the built _site folder — let the CI generate it. This prevents merge conflicts and keeps the repo clean.

.github/workflows/hugo.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
name: Deploy Hugo site to Pages
on:
  push:
    branches: ["main"]
  workflow_dispatch:
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: true
      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v3
        with:
          hugo-version: '0.124.0'
      - name: Build
        run: hugo --minify
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./public
  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/deploy-pages@v4
Output
Hugo site built and deployed to Pages on each push to main.
💡Jekyll vs Others
Jekyll is built-in, but limited. For complex sites, use Hugo or Next.js with a custom workflow. You get faster builds and more flexibility.
📊 Production Insight
We had a production outage because a developer committed a broken _site folder. Switching to CI-only builds eliminated that class of error.
🎯 Key Takeaway
Use a static site generator for maintainability, but always build via CI to avoid committing generated files.
A Record vs CNAME for Custom Domain Comparing DNS record types for GitHub Pages A Record CNAME Record Domain Type Apex domain (example.com) Subdomain (www.example.com) IP Address Dependency Requires GitHub's IP addresses Points to username.github.io IP Change Handling Manual update if GitHub IPs change Automatic resolution via DNS HTTPS Support Supported with GitHub Pages Supported with GitHub Pages Setup Complexity Higher risk of misconfiguration Simpler and less error-prone THECODEFORGE.IO
thecodeforge.io
Github Pages

Custom Domains and HTTPS

GitHub Pages supports custom domains with automatic HTTPS via Let's Encrypt. To set up, add a CNAME file to your repository's root (or configure in Settings > Pages > Custom domain). Then update your DNS: for apex domains (e.g., example.com), add an A record pointing to GitHub's IPs (185.199.108.153, 185.199.109.153, 185.199.110.153, 185.199.111.153). For subdomains (e.g., www.example.com), add a CNAME record pointing to <username>.github.io. After configuration, GitHub provisions an SSL certificate automatically. This can take up to an hour. If you use a CDN like Cloudflare, disable the proxy (orange cloud) for the DNS record, or use a full strict SSL mode.

DNS RecordsDNS
1
2
3
4
5
example.com.    A     185.199.108.153
example.com.    A     185.199.109.153
example.com.    A     185.199.110.153
example.com.    A     185.199.111.153
www.example.com. CNAME username.github.io.
Output
After DNS propagation, site accessible at https://example.com with valid HTTPS.
⚠ Cloudflare Proxy
If you use Cloudflare's proxy, disable it for GitHub Pages. Otherwise, GitHub can't verify the domain and HTTPS provisioning fails.
📊 Production Insight
We once had a domain that stopped resolving because the A records pointed to old GitHub IPs. Always use the current IPs from GitHub's documentation.
🎯 Key Takeaway
Custom domains work with A records for apex and CNAME for subdomains; HTTPS is automatic but requires DNS verification.

Enforcing HTTPS and Redirects

GitHub Pages automatically redirects HTTP to HTTPS if you have a custom domain and HTTPS is enabled. You can enforce HTTPS in Settings > Pages > Enforce HTTPS. This is critical for production: without it, users may access your site over insecure HTTP. Additionally, you can set up redirects using a _redirects file (Netlify-style) or a staticwebapp.config.json for Azure Static Web Apps, but GitHub Pages has limited redirect support. For advanced redirects, use a meta refresh in HTML or a JavaScript redirect. For SEO, prefer server-side redirects; since GitHub Pages doesn't support them natively, consider using a service like Cloudflare Workers or Netlify instead.

redirect.htmlHTML
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="refresh" content="0; url=https://example.com/new-page">
  <link rel="canonical" href="https://example.com/new-page">
</head>
<body>
  <p>Redirecting to <a href="https://example.com/new-page">new page</a>.</p>
</body>
</html>
Output
When accessed, the page immediately redirects to the new URL.
Try it live
🔥Redirect Limitations
GitHub Pages does not support server-side redirects. For 301 redirects, use a meta refresh or JavaScript. For bulk redirects, consider a different host.
📊 Production Insight
We lost SEO rankings because we used JavaScript redirects instead of 301s. For production sites with many redirects, GitHub Pages is not the right choice.
🎯 Key Takeaway
Always enforce HTTPS in Settings; use meta refresh for redirects as a workaround.

Using GitHub Actions for Advanced Deployments

GitHub Actions gives you full control over the build and deploy process. You can run tests, lint, build, and then deploy. The official actions/deploy-pages action handles deployment to the github-pages environment. For production, always pin action versions to a specific commit SHA to avoid supply chain attacks. Also, use environment secrets for API keys or tokens. A common pattern: build on push to main, deploy to a staging environment on a develop branch, and promote to production via a workflow dispatch. This mirrors a real CI/CD pipeline.

.github/workflows/deploy-staging.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
name: Deploy to Staging
on:
  push:
    branches: ["develop"]
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/checkout@v4
      - name: Build
        run: npm ci && npm run build
      - name: Deploy to Pages
        uses: actions/deploy-pages@v4
        with:
          artifact_name: staging-site
Output
On push to develop, the site is built and deployed to a staging URL (e.g., https://username.github.io/staging).
⚠ Action Pinning
Always pin actions to a full commit SHA (e.g., actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675) to prevent malicious updates.
📊 Production Insight
We had a build failure because a third-party action was updated with breaking changes. Pinning to a SHA prevented that. Also, use workflow_dispatch for manual production deployments to control timing.
🎯 Key Takeaway
Use GitHub Actions for custom build steps, staging environments, and secure deployments.

Handling 404 Pages and SEO

GitHub Pages automatically serves a 404 page if a file is not found. You can customize it by adding a 404.html file to the root of your publishing source. For SEO, ensure your 404 page is helpful and includes a link to the homepage. Also, create a sitemap.xml and robots.txt to guide search engines. For single-page applications (SPAs), you need to handle client-side routing: create a 404.html that redirects to index.html with the requested path as a hash or query parameter. This is a common pitfall — without it, direct links to SPA routes return 404.

404.htmlHTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>404 - Page Not Found</title>
  <script>
    // SPA fallback: redirect to index with the path as a query param
    sessionStorage.redirect = location.href;
    location.href = '/index.html?redirect=' + encodeURIComponent(location.pathname);
  </script>
</head>
<body>
  <h1>404</h1>
  <p>Page not found. <a href="/">Go home</a>.</p>
</body>
</html>
Output
For SPAs, the 404 page captures the requested path and redirects to the main app, which then handles routing.
Try it live
💡SPA Routing
For SPAs, you must create a custom 404.html that redirects to index.html with the original path. Otherwise, deep links break.
📊 Production Insight
We launched a React SPA on GitHub Pages and all deep links returned 404. The fix: a 404.html that redirects to index.html with the path in a query string, then the app reads it and renders the correct route.
🎯 Key Takeaway
Customize 404.html for better UX and SEO; for SPAs, implement a fallback redirect.

Performance Optimization and Caching

GitHub Pages serves static files with strong caching headers (Cache-Control: max-age=600 for HTML, 1 year for assets with hashed filenames). For production, optimize images (use WebP, compress), minify CSS/JS, and enable gzip compression (GitHub Pages does this automatically). Use a CDN like Cloudflare in front of GitHub Pages for additional caching and DDoS protection. However, be aware that GitHub Pages has a bandwidth limit of 100 GB per month (for free accounts). For high-traffic sites, consider a dedicated CDN or static hosting provider. Also, use lazy loading for images and defer non-critical scripts.

index.htmlHTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Optimized Site</title>
  <link rel="preload" href="/styles.min.css" as="style">
  <link rel="stylesheet" href="/styles.min.css">
  <script defer src="/scripts.min.js"></script>
</head>
<body>
  <img src="/hero.webp" alt="Hero" loading="lazy" width="1200" height="600">
</body>
</html>
Output
Page loads faster with preloaded CSS, deferred JS, and lazy-loaded images.
Try it live
🔥Bandwidth Limits
GitHub Pages has a soft limit of 100 GB/month. If you exceed it, your site may be throttled. Monitor usage in your account settings.
📊 Production Insight
A client's site went down due to bandwidth limits after a viral post. We moved the assets to a CDN and kept the HTML on GitHub Pages. That solved it.
🎯 Key Takeaway
Optimize assets, use caching headers, and consider a CDN for high-traffic sites.

Troubleshooting Common GitHub Pages Issues

Common issues: site not updating (check Actions tab for build failures), custom domain not resolving (DNS propagation can take 24h, verify A records), HTTPS not provisioning (ensure DNS is correct and not proxied through Cloudflare), 404 errors (check file paths and case sensitivity — GitHub Pages is case-sensitive), and build failures (check your static site generator's logs). For Jekyll, ensure your Gemfile and _config.yml are correct. For Actions, review the workflow logs. If your site is stuck on an old version, trigger a manual redeploy via the Actions tab or push an empty commit.

terminalBASH
1
2
3
# Trigger a redeploy by pushing an empty commit
git commit --allow-empty -m "Trigger redeploy"
git push
Output
A new deployment is triggered even if no files changed.
⚠ Case Sensitivity
GitHub Pages is case-sensitive. If you link to Image.jpg but the file is image.jpg, you'll get a 404. Always use consistent casing.
📊 Production Insight
We spent hours debugging a 404 only to find a capital letter mismatch. Now we enforce lowercase filenames in our CI.
🎯 Key Takeaway
Check Actions logs, DNS records, and file paths when troubleshooting; use empty commits to force redeploy.

When Not to Use GitHub Pages

GitHub Pages is not suitable for every project. Avoid it if you need server-side processing (PHP, Node.js, Python), a database, user authentication, or real-time features. Also, if you require custom redirects (301/302), advanced caching rules, or a Web Application Firewall (WAF), look elsewhere. For e-commerce, membership sites, or any site handling sensitive data, GitHub Pages lacks the necessary security controls. Finally, if your site exceeds 1 GB in size or 100 GB monthly bandwidth, you'll hit limits. For those cases, consider Vercel, Netlify, AWS S3 + CloudFront, or a VPS.

comparison.txtTEXT
1
2
3
4
5
6
7
8
9
10
GitHub Pages:
- Free, simple, Git-integrated
- Static only, no backend
- 1 GB repo limit, 100 GB bandwidth
- Limited redirects, no WAF

Alternatives:
- Netlify: Forms, functions, redirects, 100 GB bandwidth free
- Vercel: Serverless functions, edge caching, analytics
- AWS S3 + CloudFront: Scalable, full control, pay-as-you-go
Output
Choose based on your needs: static simplicity vs dynamic features.
🔥Know Your Limits
GitHub Pages is not a general-purpose web host. If your project outgrows it, migrate early to avoid emergency moves.
📊 Production Insight
We built a prototype on GitHub Pages that later needed a contact form. We had to migrate to Netlify, which cost us a weekend. Evaluate requirements upfront.
🎯 Key Takeaway
GitHub Pages is for static sites only; for dynamic features, use a platform that supports server-side logic.
● Production incidentPOST-MORTEMseverity: high

Custom Domain Resolved to Wrong Server During Product Launch

Symptom
A team launched a product with a landing page on GitHub Pages. The CNAME file was accidentally deleted in a merge conflict resolution. DNS propagated and the custom domain resolved to a 404. The launch page was down for 3 hours.
Assumption
The DNS changes propagated correctly (checked with dig), but the site returned 404. The team assumed a GitHub Pages outage.
Root cause
1. The CNAME file in the repository root told GitHub which custom domain to serve. 2. A merge conflict during a team member's PR accidentally deleted the CNAME file. 3. Without the CNAME file, GitHub stopped serving the custom domain. 4. DNS still pointed to GitHub's servers (can't change DNS quickly). 5. Visitors hit GitHub's servers, which had no mapping from the custom domain to the repo, returning a 404. 6. The team didn't notice because the CI preview URL (github.io) still worked.
Fix
1. Identified the missing CNAME file via git log --all --full-history -- CNAME. 2. Found the merge commit that deleted it. 3. Restored the CNAME file: git revert <merge-sha> (or manually recreated it). 4. Waited 5 minutes for GitHub to detect the CNAME file and re-enable the custom domain. 5. Added a CI check: test -f CNAME && echo 'CNAME exists' || exit 1. 6. Added a post-deploy check that fetches the custom domain URL and verifies HTTP 200.
Key lesson
  • CNAME file must exist in the repo for custom domains to work.
  • A deleted CNAME file causes the custom domain to 404.
  • Add CI checks that verify CNAME exists.
  • Monitor the custom domain URL with a health check — don't rely on the github.io preview URL.
Production debug guideUse this when production is on fire3 entries
Symptom · 01
Need to host a project documentation site
Fix
Use GitHub Pages from the repo's /docs folder or gh-pages branch
Symptom · 02
Need a personal portfolio site
Fix
Create <username>.github.io repo — serves automatically from main branch
Symptom · 03
Need a custom domain
Fix
Create a CNAME record pointing to <username>.github.io, add CNAME file to repo
⚙ Quick Reference
10 commands from this guide
FileCommand / CodePurpose
.githubworkflowspages.ymlname: Deploy to GitHub PagesWhat Is GitHub Pages and Why Use It?
terminalgit init my-siteSetting Up Your Repository for GitHub Pages
.githubworkflowshugo.ymlname: Deploy Hugo site to PagesUsing a Static Site Generator with GitHub Pages
DNS Recordsexample.com. A 185.199.108.153Custom Domains and HTTPS
redirect.htmlEnforcing HTTPS and Redirects
.githubworkflowsdeploy-staging.ymlname: Deploy to StagingUsing GitHub Actions for Advanced Deployments
404.htmlHandling 404 Pages and SEO
index.htmlPerformance Optimization and Caching
terminalgit commit --allow-empty -m "Trigger redeploy"Troubleshooting Common GitHub Pages Issues
comparison.txtGitHub Pages:When Not to Use GitHub Pages

Key takeaways

1
GitHub Pages serves static sites from repo branches
no server needed
2
CNAME file maps custom domains
deleting it breaks the site
3
Enable 'Enforce HTTPS' for free TLS via Let's Encrypt
4
Add CI checks to verify CNAME and URL health post-deploy
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Can I use a custom domain with GitHub Pages?
02
How do I deploy a static site generator like Hugo or Next.js to GitHub Pages?
03
Why is my GitHub Pages site not updating after I push changes?
04
Can I use GitHub Pages for a single-page application (SPA) with client-side routing?
05
What are the limitations of GitHub Pages for production use?
06
How do I set up a staging environment with GitHub Pages?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Verified
production tested
July 12, 2026
last updated
2,406
articles · all by Naren
🔥

That's Git. Mark it forged?

4 min read · try the examples if you haven't

Previous
GitHub Issues and Projects: Track Work Beyond Code
35 / 47 · Git
Next
GitHub CLI: Manage Repositories from the Terminal