
Github Pages
- 51 installs
- 6 repo stars
- Updated July 22, 2026
- julianobarbosa/claude-code-skills
Helps with ai & agent building tasks.
About
github-pages is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- github-pages
- AI & Agent Building
- AI-coding skill
Github Pages by the numbers
- 51 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #7,187 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/julianobarbosa/claude-code-skills --skill github-pagesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 51 |
|---|---|
| repo stars | ★ 6 |
| Last updated | July 22, 2026 |
| Repository | julianobarbosa/claude-code-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
GitHub Pages Skill
Complete guide for deploying, configuring, and managing GitHub Pages sites.
Quick Reference
| Site Type | Repository Name | URL Pattern |
|---|---|---|
| User/Org Site | <username>.github.io | https://<username>.github.io |
| Project Site | Any repository name | https://<username>.github.io/<repo> |
Availability:
- Public repos: GitHub Free (all plans)
- Private repos: GitHub Pro, Team, Enterprise
---
Workflow Routing
When executing a workflow, output this notification directly:
Running the **WorkflowName** workflow from the **GithubPages** skill...| Workflow | Trigger | File |
|---|---|---|
| QuickStart | "setup github pages", "create pages site" | workflows/QuickStart.md |
| CustomDomain | "add custom domain", "configure domain" | workflows/CustomDomain.md |
| JekyllSetup | "setup jekyll", "add jekyll theme" | workflows/JekyllSetup.md |
| ActionsWorkflow | "custom build", "github actions for pages" | workflows/ActionsWorkflow.md |
| Troubleshoot | "pages not working", "fix github pages" | workflows/Troubleshoot.md |
| Deploy | "deploy to github pages", "publish site" | workflows/Deploy.md |
---
Examples
Example 1: Create a new GitHub Pages site
User: "Setup GitHub Pages for my project"
→ Invokes QuickStart workflow
→ Checks if user/org or project site
→ Configures publishing source (branch or Actions)
→ Creates initial content structure
→ Verifies deploymentExample 2: Add custom domain
User: "Add my domain example.com to GitHub Pages"
→ Invokes CustomDomain workflow
→ Determines domain type (apex vs subdomain)
→ Provides DNS configuration instructions
→ Adds CNAME file or configures via Settings
→ Enables HTTPS enforcementExample 3: Setup Jekyll theme
User: "Add a theme to my GitHub Pages site"
→ Invokes JekyllSetup workflow
→ Lists available supported themes
→ Configures _config.yml
→ Sets up custom CSS/layouts if neededExample 4: Deploy with custom build
User: "Deploy my Next.js site to GitHub Pages"
→ Invokes ActionsWorkflow workflow
→ Creates custom GitHub Actions workflow
→ Configures build process
→ Sets up artifact deployment---
Site Types
User/Organization Site
Requirements:
- Repository name MUST be
<username>.github.io - Only ONE per account
- Publishes from default branch
Setup:
# Create repository named exactly: username.github.io
# Enable Pages in Settings > Pages
# Select source branchProject Site
Requirements:
- Can use any repository
- Multiple project sites allowed
- URL includes repository name
Setup:
# Any repository works
# Enable Pages in Settings > Pages
# Choose: branch (root or /docs) OR GitHub Actions---
Publishing Sources
Option 1: Deploy from Branch
Best for: Jekyll sites, simple static sites
Configuration: 1. Go to Settings > Pages 2. Select "Deploy from a branch" 3. Choose branch (main, gh-pages, etc.) 4. Choose folder: / (root) or /docs
Behavior:
- Pushes to branch trigger automatic builds
- Jekyll processes Markdown by default
- CNAME file auto-created for custom domains
Option 2: GitHub Actions Workflow
Best for: Custom builds, non-Jekyll generators
Configuration: 1. Go to Settings > Pages 2. Select "GitHub Actions" 3. Create workflow file in .github/workflows/
Behavior:
- Full control over build process
- Works with Hugo, Gatsby, Next.js, etc.
- Artifacts uploaded and deployed
---
Jekyll Integration
Auto-Enabled Plugins
These plugins work automatically on GitHub Pages:
| Plugin | Purpose |
|---|---|
| jekyll-coffeescript | CoffeeScript support |
| jekyll-default-layout | Automatic layouts |
| jekyll-gist | GitHub Gist embedding |
| jekyll-github-metadata | Repository metadata |
| jekyll-optional-front-matter | Optional YAML front matter |
| jekyll-paginate | Pagination |
| jekyll-readme-index | README as index |
| jekyll-titles-from-headings | Auto-generate titles |
| jekyll-relative-links | Convert relative links |
Supported Themes
Available without additional configuration:
- Architect
- Cayman
- Dinky
- Hacker
- Leap day
- Merlot
- Midnight
- Minima
- Minimal
- Modernist
- Slate
- Tactile
- Time machine
Usage in `_config.yml`:
theme: jekyll-theme-minimal
title: My Site
description: Site descriptionRemote Themes
Use any Jekyll theme from GitHub:
remote_theme: owner/repo-name---
Custom Domains
Domain Types
| Type | Example | DNS Record |
|---|---|---|
| Apex | example.com | A or ALIAS |
| WWW Subdomain | www.example.com | CNAME |
| Custom Subdomain | blog.example.com | CNAME |
DNS Configuration
For Apex Domains (A Records):
185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153For Apex Domains (AAAA Records - IPv6):
2606:50c0:8000::153
2606:50c0:8001::153
2606:50c0:8002::153
2606:50c0:8003::153For Subdomains (CNAME Record):
www.example.com → username.github.io
blog.example.com → username.github.ioVerification Commands
# Check A records
dig example.com +noall +answer -t A
# Check AAAA records
dig example.com +noall +answer -t AAAA
# Check CNAME records
dig www.example.com +nostats +nocomments +nocmd---
Usage Limits
| Resource | Limit |
|---|---|
| Repository size | 1 GB (recommended) |
| Published site size | 1 GB (maximum) |
| Bandwidth | 100 GB/month (soft) |
| Builds | 10/hour (soft, branch only) |
| Deployment timeout | 10 minutes |
Restrictions:
- No server-side languages (PHP, Python, Ruby)
- No commercial transactions or e-commerce
- Must comply with GitHub Terms of Service
---
Security Best Practices
1. Verify custom domains - Prevents domain takeover attacks 2. Avoid wildcard DNS - *.example.com creates security risks 3. Enable HTTPS - Always enforce HTTPS after certificate provisioning 4. Don't expose secrets - Public sites accessible even from private repos 5. Update DNS promptly - If disabling site, update/remove DNS records
---
Common Issues & Solutions
| Issue | Solution |
|---|---|
| Site not publishing | Check branch/folder settings, verify entry file exists |
| 404 errors | Ensure index.html, index.md, or README.md at root |
| Custom domain not working | Wait 24h for DNS propagation, verify records with dig |
| HTTPS not available | Wait up to 1 hour after DNS verification |
| Mixed content warnings | Change http:// to https:// in all assets |
| Build failures | Check Actions tab for error logs |
---
File References
| Topic | Reference File |
|---|---|
| DNS Configuration | references/DnsConfiguration.md |
| Jekyll Configuration | references/JekyllConfiguration.md |
| Actions Workflows | references/ActionsWorkflows.md |
| Troubleshooting Guide | references/Troubleshooting.md |
| Best Practices | references/BestPractices.md |
---
Scripts
| Script | Purpose |
|---|---|
scripts/verify-dns.sh | Verify DNS configuration for custom domains |
scripts/check-site-status.sh | Check if GitHub Pages site is live |
---
External Documentation
---
Gotchas
- GitHub Pages caches at the CDN level for ~10 minutes — recently-pushed changes may not appear immediately; check via
?nocache=$(date +%s)query param. - Custom domain requires CNAME record AND a `CNAME` file in the repo — missing either breaks HTTPS cert generation, often silently.
- Jekyll plugins must be on GitHub's whitelist —
Gemfile-only plugins fail build silently in some old configs. - Branch-vs-folder source:
gh-pagesbranch vs/docsfolder on main vs/on main — switching sources requires an explicit redeploy to take effect. - HTTPS enforcement is per-Page-property — switching from Settings UI doesn't revoke HTTP-only access immediately; old caches serve HTTP until they expire.
- Build error logs are in Actions UI, NOT in Pages settings — first-time users debug for hours in the wrong place.
GitHub Actions Workflows Reference
Complete reference for deploying GitHub Pages with custom Actions workflows.
---
Core Actions
Official GitHub Pages Actions
| Action | Purpose |
|---|---|
actions/checkout@v4 | Clone repository |
actions/configure-pages@v4 | Setup Pages configuration |
actions/upload-pages-artifact@v3 | Package build output |
actions/deploy-pages@v4 | Deploy to GitHub Pages |
---
Required Configuration
Workflow Permissions
permissions:
contents: read
pages: write
id-token: writeConcurrency Control
concurrency:
group: "pages"
cancel-in-progress: falseEnvironment Configuration
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}---
Workflow Templates
Static HTML Site
For sites with no build step required.
name: Deploy static site to Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: '.'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4Jekyll Site
name: Deploy Jekyll site to 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:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
- name: Setup Pages
id: pages
uses: actions/configure-pages@v4
- name: Build with Jekyll
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
env:
JEKYLL_ENV: production
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4Hugo Site
name: Deploy Hugo site to 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
env:
HUGO_VERSION: 0.121.0
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: ${{ env.HUGO_VERSION }}
extended: true
- name: Setup Pages
id: pages
uses: actions/configure-pages@v4
- name: Build with Hugo
env:
HUGO_ENVIRONMENT: production
HUGO_ENV: production
run: |
hugo \
--gc \
--minify \
--baseURL "${{ steps.pages.outputs.base_url }}/"
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./public
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4Next.js (Static Export)
name: Deploy Next.js site to 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:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Setup Pages
uses: actions/configure-pages@v4
with:
static_site_generator: next
- name: Install dependencies
run: npm ci
- name: Build with Next.js
run: npm run build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./out
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4Note: Requires next.config.js with:
const nextConfig = {
output: 'export',
images: { unoptimized: true }
}
module.exports = nextConfigGatsby
name: Deploy Gatsby site to 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:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Setup Pages
uses: actions/configure-pages@v4
with:
static_site_generator: gatsby
- name: Install dependencies
run: npm ci
- name: Build with Gatsby
run: npm run build
env:
PREFIX_PATHS: 'true'
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./public
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4Astro
name: Deploy Astro site to 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:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build with Astro
run: npm run build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./dist
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4MkDocs
name: Deploy MkDocs site to 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:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
cache: 'pip'
- name: Install dependencies
run: pip install mkdocs mkdocs-material
- name: Build with MkDocs
run: mkdocs build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./site
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4VuePress / VitePress
name: Deploy VitePress site to 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:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build with VitePress
run: npm run docs:build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/.vitepress/dist
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4---
Action Configuration Options
configure-pages
- uses: actions/configure-pages@v4
with:
# Static site generator type
static_site_generator: "" # next, nuxt, gatsby, sveltekit
# Custom token
token: ${{ secrets.GITHUB_TOKEN }}
# Enable pages (default: true)
enablement: trueOutputs:
base_url- Full base URLorigin- Site originhost- Site hostbase_path- Base path (for project sites)
upload-pages-artifact
- uses: actions/upload-pages-artifact@v3
with:
# Path to upload
path: ./build
# Artifact name
name: github-pages
# Retention period
retention-days: 1deploy-pages
- uses: actions/deploy-pages@v4
with:
# Custom token
token: ${{ secrets.GITHUB_TOKEN }}
# Deployment timeout
timeout: 600000 # 10 minutes
# Error count threshold
error_count: 10
# Reporting interval
reporting_interval: 5000 # 5 seconds
# Artifact name
artifact_name: github-pagesOutputs:
page_url- Deployed site URL
---
Advanced Patterns
Caching Dependencies
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # or 'yarn', 'pnpm'
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
cache: 'pip'Deploy Only on Tag
on:
push:
tags:
- 'v*'Deploy with Manual Approval
environment:
name: productionThen configure environment protection rules in repository settings.
Conditional Deployment
- name: Deploy to GitHub Pages
if: github.ref == 'refs/heads/main'
uses: actions/deploy-pages@v4Matrix Build
jobs:
build:
strategy:
matrix:
node-version: [18, 20]
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}---
Debugging
View Logs
# List workflow runs
gh run list --workflow=deploy.yml
# View specific run
gh run view <run-id> --log
# View failed steps only
gh run view <run-id> --log-failedEnable Debug Logging
Add repository secrets:
ACTIONS_RUNNER_DEBUG:trueACTIONS_STEP_DEBUG:true
Common Errors
Permission denied:
- Ensure workflow has required permissions
- Check repository settings allow Actions deployments
Artifact not found:
- Verify upload-pages-artifact path exists
- Check build output directory
Timeout:
- Increase timeout in deploy-pages action
- Optimize build process
---
Best Practices
1. Separate build and deploy jobs - Easier debugging 2. Use caching - Faster builds 3. Pin action versions - Reproducible builds 4. Enable concurrency control - Prevent race conditions 5. Use workflow_dispatch - Allow manual triggers 6. Store secrets securely - Use repository secrets 7. Test locally first - Verify build before pushing
Best Practices Reference
Recommended practices for GitHub Pages sites.
---
Site Setup
Choose the Right Publishing Source
| Use Case | Recommended Source |
|---|---|
| Simple static HTML | Branch (root) |
| Jekyll blog | Branch (root) |
| Documentation in repo | Branch (/docs) |
| Custom build (Hugo, Next.js) | GitHub Actions |
| Non-Jekyll generator | GitHub Actions |
Repository Structure
For Branch Deployment:
.
├── index.html # or index.md
├── _config.yml # Jekyll config
├── assets/
│ ├── css/
│ ├── images/
│ └── js/
├── _posts/ # Blog posts
├── _layouts/ # Custom layouts
├── _includes/ # Partials
└── CNAME # Custom domain (if used)For Actions Deployment:
.
├── src/ # Source files
├── public/ # Static assets
├── .github/
│ └── workflows/
│ └── deploy.yml # Deployment workflow
├── package.json # Dependencies
└── .nojekyll # Skip Jekyll processing---
Security
Domain Security
1. Always verify domains before adding to prevent takeover attacks 2. Never use wildcard DNS (*.example.com) - creates security risk 3. Update DNS immediately when disabling Pages 4. Monitor domain records for unauthorized changes
Content Security
1. Never commit secrets - API keys, passwords, tokens 2. Use .gitignore for sensitive files 3. Remember: public sites from private repos - Private repos can have public Pages
# .gitignore
.env
.env.local
*.pem
secrets/HTTPS
1. Always enable HTTPS after certificate provisioning 2. Fix mixed content - No HTTP resources on HTTPS sites 3. Use protocol-relative URLs when possible: //example.com/resource
---
Performance
Keep Sites Small
| Resource | Recommended Limit |
|---|---|
| Repository size | < 1 GB |
| Site size | < 1 GB |
| Individual files | < 100 MB |
| Total images | Optimize before upload |
Optimize Assets
Images:
# Compress images before upload
# Use WebP format when possible
# Implement lazy loading
<img loading="lazy" src="image.webp" alt="Description">CSS/JS:
# Jekyll: Use Sass compression
sass:
style: compressedFonts:
- Use system fonts when possible
- Subset custom fonts
- Use
font-display: swap
Caching Strategy
GitHub Pages sets these cache headers:
- Static assets: 10 minutes
- HTML: Short cache with revalidation
Cache busting for updates:
<link rel="stylesheet" href="/assets/css/style.css?v=1.0.0">---
SEO
Essential Meta Tags
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ page.title }} | {{ site.title }}</title>
<meta name="description" content="{{ page.description | default: site.description }}">
<!-- Open Graph -->
<meta property="og:title" content="{{ page.title }}">
<meta property="og:description" content="{{ page.description }}">
<meta property="og:image" content="{{ page.image | absolute_url }}">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
</head>Use Jekyll SEO Tag Plugin
# _config.yml
plugins:
- jekyll-seo-tag<!-- In layout -->
{% seo %}Sitemap
# _config.yml
plugins:
- jekyll-sitemapAutomatically generates /sitemap.xml.
Canonical URLs
<link rel="canonical" href="{{ page.url | absolute_url }}">---
Accessibility
Semantic HTML
<header>...</header>
<nav>...</nav>
<main>
<article>...</article>
<aside>...</aside>
</main>
<footer>...</footer>Alt Text
Color Contrast
- Text: minimum 4.5:1 contrast ratio
- Large text: minimum 3:1 contrast ratio
- Use tools like WebAIM Contrast Checker
Keyboard Navigation
<a href="#main-content" class="skip-link">Skip to content</a>---
Content Organization
URL Structure
# Clean URLs
permalink: pretty
# Custom permalinks
permalink: /:categories/:title/Good URL examples:
/about//blog/2024/my-post//docs/getting-started/
Avoid:
/about.html/page1//2024/01/15/my-post.html
Navigation
# _data/navigation.yml
main:
- title: Home
url: /
- title: Blog
url: /blog/
- title: About
url: /about/<nav>
{% for item in site.data.navigation.main %}
<a href="{{ item.url }}" {% if page.url == item.url %}aria-current="page"{% endif %}>
{{ item.title }}
</a>
{% endfor %}
</nav>---
Development Workflow
Local Development
# Always test locally before pushing
bundle exec jekyll serve --livereload
# Test with drafts
bundle exec jekyll serve --drafts
# Build for production
JEKYLL_ENV=production bundle exec jekyll buildVersion Control
# .gitignore
_site/
.jekyll-cache/
.jekyll-metadata
.sass-cache/
node_modules/
.bundle/
vendor/Keep Dependencies Updated
# Update GitHub Pages gem
bundle update github-pages
# Check for outdated gems
bundle outdated---
Custom Domains
Recommended Configuration
1. Use www subdomain as primary - most stable 2. Configure both apex and www - automatic redirects 3. Verify domain ownership - prevents takeover 4. Enable HTTPS - always enforce
DNS Best Practices
# Apex domain (A records)
@ → 185.199.108.153
@ → 185.199.109.153
@ → 185.199.110.153
@ → 185.199.111.153
# WWW subdomain (CNAME)
www → username.github.io---
Deployment
Branch Deployment Best Practices
1. Use protected branch for publishing source 2. Require reviews before merging 3. Test in preview before production
Actions Deployment Best Practices
1. Separate build and deploy jobs 2. Use caching for faster builds 3. Pin action versions for stability 4. Enable concurrency control
concurrency:
group: "pages"
cancel-in-progress: falsePre-Deploy Checklist
- [ ] Content reviewed for accuracy
- [ ] Links tested
- [ ] Images optimized
- [ ] No sensitive data exposed
- [ ] Meta tags complete
- [ ] Local build successful
- [ ] Accessibility checked
---
Monitoring
Check Site Health
# Verify site is responding
curl -IL https://example.github.io
# Check response time
curl -w "@curl-format.txt" -o /dev/null -s https://example.github.ioMonitor Builds
# Set up notifications for failed builds
# Check Actions tab regularly
gh run list --workflow=pages --limit 5Track Analytics
Use privacy-respecting analytics:
- Plausible Analytics
- Simple Analytics
- Fathom Analytics
Or self-hosted:
- Umami
- GoatCounter
---
Common Mistakes to Avoid
| Mistake | Better Approach |
|---|---|
Committing _site/ | Add to .gitignore |
| Using absolute URLs | Use `{{ '' |
| Large unoptimized images | Compress and resize |
| No custom 404 page | Create 404.html |
| Forgetting baseurl | Set for project sites |
| No meta descriptions | Add to all pages |
| Breaking changes on main | Use branches and PRs |
| Ignoring build errors | Monitor and fix promptly |
---
Checklist for New Sites
Initial Setup
- [ ] Repository created with correct name
- [ ] GitHub Pages enabled
- [ ] Publishing source configured
- [ ] Entry file (index.html/md) created
- [ ] Basic content added
Configuration
- [ ] _config.yml configured (Jekyll)
- [ ] Theme selected
- [ ] Plugins enabled
- [ ] Baseurl set (project sites)
- [ ] CNAME added (custom domain)
Content
- [ ] Homepage complete
- [ ] About page
- [ ] 404 page
- [ ] Navigation structure
- [ ] Meta tags and SEO
Quality
- [ ] Mobile responsive
- [ ] Links tested
- [ ] Images optimized
- [ ] Accessibility checked
- [ ] Performance acceptable
Security & Compliance
- [ ] HTTPS enabled
- [ ] No secrets exposed
- [ ] Domain verified
- [ ] Privacy policy (if collecting data)
DNS Configuration Reference
Complete DNS configuration guide for GitHub Pages custom domains.
---
Overview
GitHub Pages supports three types of custom domains: 1. Apex domains (bare/root): example.com 2. WWW subdomains: www.example.com 3. Custom subdomains: blog.example.com, docs.example.com
Recommendation: Always configure BOTH apex AND www subdomain for automatic redirects.
---
DNS Record Types
A Records (IPv4)
Used for apex domains. Point directly to GitHub's IP addresses.
Type: A
Host: @ (or leave blank)
TTL: 3600 (or 1 hour)
Values (add ALL four):
185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153AAAA Records (IPv6)
Optional but recommended for IPv6 support.
Type: AAAA
Host: @ (or leave blank)
TTL: 3600
Values (add ALL four):
2606:50c0:8000::153
2606:50c0:8001::153
2606:50c0:8002::153
2606:50c0:8003::153CNAME Records
Used for subdomains. Point to your GitHub Pages URL.
Type: CNAME
Host: www (or your subdomain)
TTL: 3600
Value: <username>.github.io.Note: The trailing dot (.) is standard DNS notation and may or may not be required by your DNS provider.
ALIAS/ANAME Records
Alternative to A records for apex domains (if your DNS provider supports it).
Type: ALIAS (or ANAME)
Host: @ (or leave blank)
TTL: 3600
Value: <username>.github.io.---
Configuration by Domain Type
Apex Domain Only
example.com → A records pointing to GitHub IPsDNS Records:
| Type | Host | Value |
|---|---|---|
| A | @ | 185.199.108.153 |
| A | @ | 185.199.109.153 |
| A | @ | 185.199.110.153 |
| A | @ | 185.199.111.153 |
WWW Subdomain Only
www.example.com → CNAME to username.github.ioDNS Records:
| Type | Host | Value |
|---|---|---|
| CNAME | www | username.github.io |
Both Apex and WWW (Recommended)
example.com → A records to GitHub IPs
www.example.com → CNAME to username.github.ioDNS Records:
| Type | Host | Value |
|---|---|---|
| A | @ | 185.199.108.153 |
| A | @ | 185.199.109.153 |
| A | @ | 185.199.110.153 |
| A | @ | 185.199.111.153 |
| CNAME | www | username.github.io |
Behavior: GitHub automatically redirects between apex and www based on which is configured in Pages settings.
Custom Subdomain
blog.example.com → CNAME to username.github.ioDNS Records:
| Type | Host | Value |
|---|---|---|
| CNAME | blog | username.github.io |
---
DNS Provider Examples
Cloudflare
1. Log into Cloudflare dashboard 2. Select your domain 3. Go to DNS tab 4. Click "Add record"
For apex domain:
- Type: A
- Name: @
- IPv4 address: 185.199.108.153
- Proxy status: DNS only (gray cloud) recommended initially
- TTL: Auto
For www:
- Type: CNAME
- Name: www
- Target: username.github.io
- Proxy status: DNS only recommended initially
Namecheap
1. Log into Namecheap 2. Domain List > Manage 3. Advanced DNS tab
For apex domain:
- Type: A Record
- Host: @
- Value: 185.199.108.153
- TTL: Automatic
For www:
- Type: CNAME Record
- Host: www
- Value: username.github.io.
- TTL: Automatic
GoDaddy
1. Log into GoDaddy 2. My Products > DNS 3. Add record
For apex domain:
- Type: A
- Name: @
- Value: 185.199.108.153
- TTL: 1 Hour
For www:
- Type: CNAME
- Name: www
- Value: username.github.io
- TTL: 1 Hour
Google Domains / Squarespace Domains
1. Log into Google Domains 2. DNS tab 3. Manage custom records
For apex domain:
- Host name: (leave blank)
- Type: A
- TTL: 1H
- Data: 185.199.108.153
For www:
- Host name: www
- Type: CNAME
- TTL: 1H
- Data: username.github.io.
---
Verification Commands
Check A Records
dig example.com +noall +answer -t AExpected output:
example.com. 3600 IN A 185.199.108.153
example.com. 3600 IN A 185.199.109.153
example.com. 3600 IN A 185.199.110.153
example.com. 3600 IN A 185.199.111.153Check AAAA Records
dig example.com +noall +answer -t AAAAExpected output:
example.com. 3600 IN AAAA 2606:50c0:8000::153
example.com. 3600 IN AAAA 2606:50c0:8001::153
example.com. 3600 IN AAAA 2606:50c0:8002::153
example.com. 3600 IN AAAA 2606:50c0:8003::153Check CNAME Records
dig www.example.com +noall +answer -t CNAMEExpected output:
www.example.com. 3600 IN CNAME username.github.io.Check All Records for Domain
dig example.com ANY +noall +answerCheck Global Propagation
Use online tools:
- https://www.whatsmydns.net/
- https://dnschecker.org/
---
CAA Records (HTTPS Certificates)
For HTTPS to work, Let's Encrypt must be able to issue certificates.
If you have CAA records configured:
Ensure at least one allows Let's Encrypt:
Type: CAA
Host: @
Value: 0 issue "letsencrypt.org"Check existing CAA records:
dig example.com CAAIf no CAA records exist: Let's Encrypt can issue certificates (default behavior).
---
Common Issues
DNS Not Propagating
Causes:
- TTL from previous records not expired
- Caching at various DNS levels
- Wrong DNS server being queried
Solutions:
- Wait up to 24-48 hours
- Use
dig @8.8.8.8 example.com Ato query Google DNS directly - Lower TTL before making changes, then change
Conflicting Records
Problem: Old records conflicting with new GitHub Pages records.
Solution: Remove these before adding GitHub Pages records:
- Old A records
- Old AAAA records
- Old CNAME for @
- Wildcard records
Wildcard Records Warning
Never use wildcard DNS records for GitHub Pages:
# DO NOT DO THIS
*.example.com → anythingRisk: Creates domain takeover vulnerability. Attackers can claim any unused subdomain.
Wrong Record Type
Problem: Using CNAME for apex domain.
Solution: Most DNS providers don't allow CNAME on apex. Use A records or ALIAS/ANAME if supported.
Missing Trailing Dot
Problem: CNAME value without trailing dot.
Note: Some providers require username.github.io. (with dot), others require username.github.io (without). Check your provider's documentation.
---
Security Considerations
1. Verify domain ownership in GitHub before adding DNS records 2. Remove DNS records if disabling GitHub Pages 3. Monitor for unauthorized changes to DNS records 4. Avoid wildcard records - security risk 5. Use DNSSEC if available from your registrar
---
Quick Reference Table
| Domain Type | Record Type | Host | Value |
|---|---|---|---|
| Apex | A | @ | 185.199.108.153 |
| Apex | A | @ | 185.199.109.153 |
| Apex | A | @ | 185.199.110.153 |
| Apex | A | @ | 185.199.111.153 |
| Apex (IPv6) | AAAA | @ | 2606:50c0:8000::153 |
| Apex (IPv6) | AAAA | @ | 2606:50c0:8001::153 |
| Apex (IPv6) | AAAA | @ | 2606:50c0:8002::153 |
| Apex (IPv6) | AAAA | @ | 2606:50c0:8003::153 |
| WWW | CNAME | www | username.github.io |
| Custom Sub | CNAME | blog | username.github.io |
| Apex (alt) | ALIAS | @ | username.github.io |
Jekyll Configuration Reference
Complete reference for configuring Jekyll on GitHub Pages.
---
_config.yml
The primary configuration file for Jekyll sites.
Minimal Configuration
theme: jekyll-theme-minimal
title: My Site
description: A brief description of my siteFull Configuration
# =============================================================================
# Site Settings
# =============================================================================
title: My Site
description: >-
A longer description that can span
multiple lines for SEO purposes.
url: https://username.github.io
baseurl: "" # Use "/repo-name" for project sites
# =============================================================================
# Theme
# =============================================================================
# Supported themes (no additional setup needed)
theme: jekyll-theme-minimal
# Or use a remote theme from GitHub
# remote_theme: pages-themes/minimal@v0.2.0
# =============================================================================
# Author
# =============================================================================
author:
name: Your Name
email: you@example.com
twitter: yourhandle
github: yourusername
# =============================================================================
# Build Settings
# =============================================================================
markdown: kramdown
highlighter: rouge
permalink: pretty # or /:year/:month/:day/:title/
# Timezone for date/time
timezone: America/New_York
# =============================================================================
# Plugins
# =============================================================================
plugins:
- jekyll-feed
- jekyll-seo-tag
- jekyll-sitemap
- jekyll-paginate
- jekyll-relative-links
# =============================================================================
# Collections (optional)
# =============================================================================
collections:
projects:
output: true
permalink: /projects/:name/
docs:
output: true
permalink: /docs/:path/
# =============================================================================
# Defaults
# =============================================================================
defaults:
- scope:
path: ""
type: "posts"
values:
layout: "post"
author: "Your Name"
- scope:
path: ""
type: "pages"
values:
layout: "default"
# =============================================================================
# Pagination (requires jekyll-paginate)
# =============================================================================
paginate: 10
paginate_path: /blog/page:num/
# =============================================================================
# Exclude/Include
# =============================================================================
exclude:
- Gemfile
- Gemfile.lock
- node_modules
- vendor
- README.md
- CHANGELOG.md
- LICENSE
include:
- _pages
- .htaccess
# =============================================================================
# Kramdown Configuration
# =============================================================================
kramdown:
input: GFM
hard_wrap: false
syntax_highlighter: rouge
syntax_highlighter_opts:
block:
line_numbers: true
# =============================================================================
# Sass Configuration
# =============================================================================
sass:
style: compressed
sass_dir: _sass---
GitHub Pages Auto-Configuration
GitHub Pages automatically sets these values (cannot be overridden):
lsi: false
safe: true
source: [repository root]
incremental: false
highlighter: rouge
gist:
noscript: false
kramdown:
math_engine: mathjax
syntax_highlighter: rouge---
Supported Plugins
Auto-Enabled by GitHub Pages
| Plugin | Description |
|---|---|
| jekyll-coffeescript | CoffeeScript converter |
| jekyll-default-layout | Set default layouts |
| jekyll-gist | Gist embedding |
| jekyll-github-metadata | Repository metadata |
| jekyll-optional-front-matter | Front matter optional for some files |
| jekyll-paginate | Pagination |
| jekyll-readme-index | Use README as index |
| jekyll-titles-from-headings | Auto-generate titles |
| jekyll-relative-links | Convert relative links |
Optional Supported Plugins
Enable in _config.yml:
plugins:
- jekyll-feed # RSS/Atom feed
- jekyll-seo-tag # SEO meta tags
- jekyll-sitemap # XML sitemap
- jekyll-avatar # GitHub avatar helper
- jekyll-mentions # @mention links
- jekyll-redirect-from # Redirect pages
- jemoji # Emoji supportNote: Unsupported plugins will cause build failures. Build locally if you need unsupported plugins.
---
Supported Themes
Built-in Themes
Use with theme: in _config.yml:
| Theme Name | Config Value |
|---|---|
| Architect | jekyll-theme-architect |
| Cayman | jekyll-theme-cayman |
| Dinky | jekyll-theme-dinky |
| Hacker | jekyll-theme-hacker |
| Leap Day | jekyll-theme-leap-day |
| Merlot | jekyll-theme-merlot |
| Midnight | jekyll-theme-midnight |
| Minima | minima |
| Minimal | jekyll-theme-minimal |
| Modernist | jekyll-theme-modernist |
| Slate | jekyll-theme-slate |
| Tactile | jekyll-theme-tactile |
| Time Machine | jekyll-theme-time-machine |
Remote Themes
Use any Jekyll theme from GitHub:
remote_theme: owner/repository
# or with version
remote_theme: owner/repository@v1.0.0Examples:
remote_theme: pages-themes/cayman@v0.2.0
remote_theme: mmistakes/minimal-mistakes
remote_theme: just-the-docs/just-the-docs---
Front Matter
YAML configuration at the top of content files.
Page Front Matter
---
layout: default
title: Page Title
description: Page description for SEO
permalink: /custom-url/
nav_order: 1
parent: Parent Page
---Post Front Matter
---
layout: post
title: "Post Title"
date: 2024-01-15 10:00:00 -0500
categories: [category1, category2]
tags: [tag1, tag2]
author: Your Name
excerpt: "Custom excerpt for listings"
image: /assets/images/featured.jpg
published: true
---Draft Posts
Store in _drafts/ folder without date:
_drafts/my-upcoming-post.mdPreview drafts locally:
bundle exec jekyll serve --drafts---
Directory Structure
.
├── _config.yml # Site configuration
├── _data/ # Data files (YAML, JSON, CSV)
│ └── navigation.yml
├── _drafts/ # Unpublished posts
│ └── upcoming-post.md
├── _includes/ # Reusable partials
│ ├── header.html
│ └── footer.html
├── _layouts/ # Page templates
│ ├── default.html
│ └── post.html
├── _posts/ # Blog posts
│ └── 2024-01-15-hello-world.md
├── _sass/ # Sass partials
│ └── _custom.scss
├── assets/
│ ├── css/
│ │ └── style.scss
│ ├── images/
│ └── js/
├── _pages/ # Additional pages (if using collections)
├── index.md # Homepage
└── 404.html # Custom 404 page---
Data Files
Store structured data in _data/ folder.
YAML Example (_data/navigation.yml)
- title: Home
url: /
- title: About
url: /about/
- title: Blog
url: /blog/
- title: Contact
url: /contact/Usage in templates:
<nav>
{% for item in site.data.navigation %}
<a href="{{ item.url }}">{{ item.title }}</a>
{% endfor %}
</nav>JSON Example (_data/team.json)
[
{
"name": "Alice",
"role": "Developer",
"avatar": "/assets/images/alice.jpg"
},
{
"name": "Bob",
"role": "Designer",
"avatar": "/assets/images/bob.jpg"
}
]---
Useful Variables
Site Variables
| Variable | Description |
|---|---|
site.title | Site title |
site.description | Site description |
site.url | Site URL |
site.baseurl | Base URL path |
site.pages | All pages |
site.posts | All posts |
site.data | Data files |
site.time | Current time |
site.github | GitHub metadata |
Page Variables
| Variable | Description |
|---|---|
page.title | Page title |
page.url | Page URL |
page.date | Page date |
page.content | Page content |
page.excerpt | Page excerpt |
page.categories | Post categories |
page.tags | Post tags |
GitHub Metadata (site.github)
| Variable | Description |
|---|---|
site.github.repository_name | Repository name |
site.github.repository_url | Repository URL |
site.github.project_title | Project title |
site.github.owner_name | Owner name |
site.github.owner_url | Owner profile URL |
---
Common Liquid Patterns
Conditional Content
{% if page.title %}
<h1>{{ page.title }}</h1>
{% endif %}Loop Through Posts
{% for post in site.posts limit:5 %}
<article>
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
<time>{{ post.date | date: "%B %d, %Y" }}</time>
{{ post.excerpt }}
</article>
{% endfor %}Filter Posts by Category
{% assign tech_posts = site.posts | where: "categories", "tech" %}
{% for post in tech_posts %}
...
{% endfor %}Include Partial with Parameters
{% include header.html title="Custom Title" %}In _includes/header.html:
<header>
<h1>{{ include.title }}</h1>
</header>---
URL Helpers
<!-- Relative URL (respects baseurl) -->
<a href="{{ '/about/' | relative_url }}">About</a>
<!-- Absolute URL -->
<a href="{{ '/about/' | absolute_url }}">About</a>
<!-- Link to post -->
{% post_url 2024-01-15-hello-world %}
<!-- Link to asset -->
{{ '/assets/images/logo.png' | relative_url }}---
Local Development
Gemfile
source "https://rubygems.org"
gem "github-pages", group: :jekyll_plugins
group :jekyll_plugins do
gem "jekyll-feed"
gem "jekyll-seo-tag"
end
# Ruby 3.0+ compatibility
gem "webrick"Commands
# Install dependencies
bundle install
# Update dependencies
bundle update
# Serve with live reload
bundle exec jekyll serve --livereload
# Build only
bundle exec jekyll build
# Serve drafts
bundle exec jekyll serve --drafts
# Verbose output
bundle exec jekyll build --verboseTroubleshooting Reference
Comprehensive troubleshooting guide for GitHub Pages issues.
---
Diagnostic Commands
# Check Pages configuration
gh api repos/{owner}/{repo}/pages
# Check build status
gh api repos/{owner}/{repo}/pages/builds --jq '.[0]'
# View deployment status
gh api repos/{owner}/{repo}/deployments --jq '.[0]'
# Test site availability
curl -I https://<username>.github.io/<repo>
# Check DNS records
dig <domain> A
dig <domain> AAAA
dig www.<domain> CNAME
# View workflow logs
gh run list --workflow=pages
gh run view <run-id> --log---
Issue Categories
1. Site Not Publishing
Symptoms:
- No site at expected URL
- Repository shows no Pages deployment
- Settings shows "Your site is ready to be published"
Possible Causes & Solutions:
| Cause | Solution |
|---|---|
| Pages not enabled | Go to Settings > Pages and enable |
| Wrong publishing source | Verify branch and folder settings |
| No entry file | Add index.html, index.md, or README.md |
| Unverified email | Verify email in account settings |
| Using deploy key | Use machine user account instead |
Verification:
# Check if Pages is enabled
gh api repos/{owner}/{repo}/pages
# Check publishing source
gh api repos/{owner}/{repo}/pages --jq '.source'
# Verify entry file exists
ls -la index.html index.md README.md---
2. 404 Errors
Symptoms:
- GitHub 404 page appears
- Some pages work, others don't
- Assets not loading
Possible Causes & Solutions:
| Cause | Solution |
|---|---|
| Entry file missing | Add index.html or index.md at root |
| Wrong baseurl | Set correct baseurl for project sites |
| Case sensitivity | Match exact file names (Linux is case-sensitive) |
| Jekyll excluding files | Check _config.yml exclude settings |
| Build failure | Check Actions tab for errors |
For Project Sites:
# _config.yml
baseurl: "/repository-name"Link Format:
<!-- Wrong -->
<a href="/about">About</a>
<!-- Correct for project sites -->
<a href="{{ '/about' | relative_url }}">About</a>---
3. Custom Domain Issues
Symptoms:
- Domain shows error
- Wrong site appears
- HTTPS not available
- Verification failed
DNS Not Propagating
Verification:
# Check current DNS
dig example.com +noall +answer -t A
dig www.example.com +noall +answer -t CNAME
# Check propagation globally
# Use: whatsmydns.netSolutions:
- Wait up to 24-48 hours
- Lower TTL before making changes
- Remove conflicting records
CNAME File Issues
Requirements:
- File must be named
CNAME(uppercase) - Contains only the domain name
- No protocol prefix (no
https://) - One domain only
Correct CNAME file:
www.example.comCreate/fix CNAME:
echo "www.example.com" > CNAME
git add CNAME && git commit -m "Fix CNAME" && git pushDomain Verification
Steps: 1. Go to user/org Settings > Pages 2. Add domain 3. Add TXT record to DNS 4. Wait for verification
---
4. HTTPS Issues
Symptoms:
- "Enforce HTTPS" greyed out
- Certificate errors
- Mixed content warnings
Solutions:
Certificate not provisioning:
# Remove and re-add domain
gh api repos/{owner}/{repo}/pages --method PUT --field cname=''
# Wait 1 minute
gh api repos/{owner}/{repo}/pages --method PUT --field cname='example.com'CAA records blocking:
# Check CAA records
dig example.com CAA
# Must allow letsencrypt.org or have no CAA records
# Add if needed:
# Type: CAA
# Value: 0 issue "letsencrypt.org"Mixed content:
# Find HTTP references
grep -r "http://" --include="*.html" --include="*.md" --include="*.scss" .
# Replace with https:// or //---
5. Build Failures
Symptoms:
- Deployment never completes
- Error notification email
- Red X on Actions tab
Common Jekyll Errors:
| Error | Cause | Solution |
|---|---|---|
invalid byte sequence | Encoding issue | Save files as UTF-8 |
undefined method | Liquid syntax error | Check {{ }} and {% %} |
could not read file | Include not found | Verify _includes/ files |
invalid date | Bad date format | Use YYYY-MM-DD format |
found character that cannot start | YAML error | Check _config.yml syntax |
View build errors:
# Via Actions
gh run list --workflow=pages --limit 5
gh run view <run-id> --log-failed
# Build locally to see errors
bundle exec jekyll build --verboseCommon fixes:
# Fix YAML special characters
title: "My Site: A Subtitle" # Quote strings with colons
# Escape Liquid in code blocks
{% raw %}
{{ variable }}
{% endraw %}---
6. Changes Not Appearing
Symptoms:
- Pushed changes not visible
- Old content still showing
- Random caching
Solutions:
Verify deployment completed:
# Check latest build
gh api repos/{owner}/{repo}/pages/builds --jq '.[0].status'
# Check commit deployed
gh api repos/{owner}/{repo}/pages/builds --jq '.[0].commit'Clear caches:
# Browser: Ctrl+Shift+R (Cmd+Shift+R on Mac)
# Append cache buster
# https://site.github.io/?v=timestamp
# Clear CDN (wait)
# GitHub's CDN caches for ~10 minutesForce rebuild:
git commit --allow-empty -m "Trigger rebuild"
git push---
7. Theme Issues
Symptoms:
- Unstyled content
- Wrong theme
- Custom CSS not loading
Solutions:
Theme not applying:
# Verify exact theme name in _config.yml
theme: jekyll-theme-minimal # Correct
theme: minimal # Wrong (except for minima)Custom CSS not loading:
/* assets/css/style.scss - REQUIRES front matter */
---
---
@import "{{ site.theme }}";
/* Custom styles after the import */Remote theme issues:
# Use correct format
remote_theme: owner/repo@version---
8. Jekyll-Specific Issues
Files not being processed:
# _config.yml - include hidden or excluded files
include:
- .htaccess
- _pagesFiles being excluded: Jekyll ignores by default:
- Files starting with
_,., or# - Files ending with
~ - Files in
node_modules/,vendor/
Permalinks not working:
# _config.yml
permalink: pretty # Creates clean URLs
# Or in front matter
---
permalink: /custom-url/
------
9. Actions Workflow Issues
Permission errors:
# Ensure permissions are set
permissions:
contents: read
pages: write
id-token: writeArtifact issues:
# Verify path exists
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./build # Must match your build outputTimeout:
# Increase timeout (max 10 minutes for Pages)
- name: Deploy
uses: actions/deploy-pages@v4
with:
timeout: 600000---
Error Messages Reference
| Error Message | Likely Cause | Solution |
|---|---|---|
| "Page build failed" | Various build errors | Check Actions logs |
| "Your site is having problems" | Configuration error | Review _config.yml |
| "404 - File not found" | Missing entry file | Add index.html/index.md |
| "Certificate not yet available" | DNS pending | Wait up to 1 hour |
| "Domain is already taken" | Duplicate CNAME | Use unique domain |
| "Unable to build page" | Syntax error | Check Liquid/YAML syntax |
| "Symlink does not exist" | Broken symlink | Remove or fix symlinks |
| "Invalid date" | Wrong date format | Use YYYY-MM-DD |
| "Unable to parse _config.yml" | YAML syntax error | Validate YAML |
---
When to Contact Support
Contact GitHub Support when:
- Build failures with no clear error after troubleshooting
- DNS correctly configured but domain not working after 48 hours
- HTTPS not available after 24 hours with correct setup
- Account-specific Pages limitations
Before contacting: 1. Document all troubleshooting steps 2. Include repository name and URLs 3. Provide dig output for DNS issues 4. Include build log excerpts 5. Note when the issue started
---
Quick Fixes Checklist
- [ ] Pages enabled in Settings
- [ ] Correct publishing source (branch/folder)
- [ ] Entry file exists (index.html/index.md/README.md)
- [ ] Valid _config.yml (if using Jekyll)
- [ ] Correct baseurl for project sites
- [ ] DNS records pointing to GitHub
- [ ] No build errors in Actions tab
- [ ] Browser cache cleared
- [ ] Waited sufficient time for propagation
#!/bin/bash
# Check GitHub Pages site status
# Usage: ./check-site-status.sh username/repo [custom-domain]
set -euo pipefail
REPO="${1:-}"
CUSTOM_DOMAIN="${2:-}"
if [[ -z "$REPO" ]]; then
echo "Usage: $0 <username/repo> [custom-domain]"
echo "Example: $0 octocat/hello-world"
echo "Example: $0 octocat/hello-world example.com"
exit 1
fi
# Extract username and repo name
IFS='/' read -r USERNAME REPONAME <<< "$REPO"
if [[ -z "$USERNAME" ]] || [[ -z "$REPONAME" ]]; then
echo "Error: Invalid repository format. Use username/repo"
exit 1
fi
echo "========================================="
echo "GitHub Pages Site Status Check"
echo "Repository: $REPO"
echo "========================================="
echo ""
# Determine site URLs
if [[ "$REPONAME" == "$USERNAME.github.io" ]]; then
SITE_TYPE="User/Organization"
PAGES_URL="https://$USERNAME.github.io"
else
SITE_TYPE="Project"
PAGES_URL="https://$USERNAME.github.io/$REPONAME"
fi
echo "Site Type: $SITE_TYPE"
echo "Expected URL: $PAGES_URL"
if [[ -n "$CUSTOM_DOMAIN" ]]; then
echo "Custom Domain: https://$CUSTOM_DOMAIN"
fi
echo ""
# Function to check HTTP status
check_url() {
local url=$1
local name=$2
echo "Checking $name..."
local response
response=$(curl -sIL -w "%{http_code}" -o /dev/null --connect-timeout 10 "$url" 2>/dev/null) || response="000"
case $response in
200)
echo " ✅ $url - OK (200)"
return 0
;;
301|302)
echo " ↪️ $url - Redirect ($response)"
local redirect_url
redirect_url=$(curl -sI "$url" | grep -i "location:" | head -1 | awk '{print $2}' | tr -d '\r')
echo " → $redirect_url"
return 0
;;
404)
echo " ❌ $url - Not Found (404)"
return 1
;;
000)
echo " ❌ $url - Connection failed"
return 1
;;
*)
echo " ⚠️ $url - HTTP $response"
return 1
;;
esac
}
# Check Pages URL
echo ""
echo "========================================="
echo "Site Availability"
echo "========================================="
echo ""
check_url "$PAGES_URL" "GitHub Pages URL"
if [[ -n "$CUSTOM_DOMAIN" ]]; then
echo ""
check_url "https://$CUSTOM_DOMAIN" "Custom Domain (HTTPS)"
check_url "https://www.$CUSTOM_DOMAIN" "WWW Subdomain (HTTPS)"
fi
# Check via GitHub API (requires gh CLI)
if command -v gh &> /dev/null; then
echo ""
echo "========================================="
echo "GitHub API Status"
echo "========================================="
echo ""
# Check if authenticated
if gh auth status &> /dev/null; then
echo "Fetching Pages configuration..."
pages_info=$(gh api "repos/$REPO/pages" 2>/dev/null) || {
echo " ❌ GitHub Pages not enabled or no access"
exit 0
}
status=$(echo "$pages_info" | jq -r '.status // "unknown"')
url=$(echo "$pages_info" | jq -r '.html_url // "N/A"')
cname=$(echo "$pages_info" | jq -r '.cname // "none"')
https=$(echo "$pages_info" | jq -r '.https_enforced // false')
source_branch=$(echo "$pages_info" | jq -r '.source.branch // "N/A"')
source_path=$(echo "$pages_info" | jq -r '.source.path // "/"')
build_type=$(echo "$pages_info" | jq -r '.build_type // "N/A"')
echo ""
echo " Status: $status"
echo " URL: $url"
echo " Custom Domain: $cname"
echo " HTTPS Enforced: $https"
echo " Build Type: $build_type"
if [[ "$build_type" != "workflow" ]]; then
echo " Source Branch: $source_branch"
echo " Source Path: $source_path"
fi
# Check latest build
echo ""
echo "Latest Build:"
latest_build=$(gh api "repos/$REPO/pages/builds" --jq '.[0]' 2>/dev/null) || {
echo " No build information available"
exit 0
}
if [[ -n "$latest_build" ]] && [[ "$latest_build" != "null" ]]; then
build_status=$(echo "$latest_build" | jq -r '.status // "unknown"')
build_created=$(echo "$latest_build" | jq -r '.created_at // "N/A"')
build_commit=$(echo "$latest_build" | jq -r '.commit // "N/A"' | head -c 7)
case $build_status in
built)
echo " ✅ Status: Built successfully"
;;
building)
echo " 🔄 Status: Building..."
;;
errored)
echo " ❌ Status: Build error"
;;
*)
echo " Status: $build_status"
;;
esac
echo " Created: $build_created"
echo " Commit: $build_commit"
else
echo " No builds found (using GitHub Actions?)"
fi
else
echo " ⚠️ Not authenticated with gh CLI"
echo " Run: gh auth login"
fi
else
echo ""
echo "Tip: Install GitHub CLI (gh) for more detailed status"
echo "https://cli.github.com/"
fi
echo ""
echo "========================================="
echo "Troubleshooting"
echo "========================================="
echo ""
echo "If site is not available:"
echo " 1. Check Settings > Pages in repository"
echo " 2. Verify entry file exists (index.html, index.md, README.md)"
echo " 3. Check Actions tab for build errors"
echo " 4. Wait up to 10 minutes for deployment"
echo ""
echo "Documentation: https://docs.github.com/en/pages"
#!/bin/bash
# Verify DNS configuration for GitHub Pages custom domain
# Usage: ./verify-dns.sh example.com
set -euo pipefail
DOMAIN="${1:-}"
if [[ -z "$DOMAIN" ]]; then
echo "Usage: $0 <domain>"
echo "Example: $0 example.com"
exit 1
fi
# GitHub Pages IP addresses
GITHUB_IPS=(
"185.199.108.153"
"185.199.109.153"
"185.199.110.153"
"185.199.111.153"
)
GITHUB_IPV6=(
"2606:50c0:8000::153"
"2606:50c0:8001::153"
"2606:50c0:8002::153"
"2606:50c0:8003::153"
)
echo "========================================="
echo "GitHub Pages DNS Verification"
echo "Domain: $DOMAIN"
echo "========================================="
echo ""
# Check if it's an apex domain or subdomain
if [[ "$DOMAIN" == www.* ]]; then
IS_SUBDOMAIN=true
RECORD_TYPE="CNAME"
else
IS_SUBDOMAIN=false
RECORD_TYPE="A"
fi
# Function to check A records
check_a_records() {
local domain=$1
echo "Checking A records for $domain..."
echo ""
local a_records
a_records=$(dig "$domain" +noall +answer -t A | awk '{print $5}')
if [[ -z "$a_records" ]]; then
echo " ❌ No A records found"
return 1
fi
local found=0
for ip in "${GITHUB_IPS[@]}"; do
if echo "$a_records" | grep -q "$ip"; then
echo " ✅ Found: $ip"
((found++))
else
echo " ❌ Missing: $ip"
fi
done
if [[ $found -eq 4 ]]; then
echo ""
echo " All GitHub Pages A records configured correctly!"
return 0
else
echo ""
echo " ⚠️ Only $found/4 GitHub Pages IPs configured"
return 1
fi
}
# Function to check AAAA records
check_aaaa_records() {
local domain=$1
echo ""
echo "Checking AAAA records for $domain..."
echo ""
local aaaa_records
aaaa_records=$(dig "$domain" +noall +answer -t AAAA | awk '{print $5}')
if [[ -z "$aaaa_records" ]]; then
echo " ⚠️ No AAAA records found (IPv6 optional but recommended)"
return 0
fi
local found=0
for ip in "${GITHUB_IPV6[@]}"; do
if echo "$aaaa_records" | grep -qi "$ip"; then
echo " ✅ Found: $ip"
((found++))
fi
done
if [[ $found -eq 4 ]]; then
echo ""
echo " All GitHub Pages AAAA records configured correctly!"
else
echo ""
echo " ⚠️ Only $found/4 GitHub Pages IPv6 addresses configured"
fi
}
# Function to check CNAME records
check_cname_records() {
local domain=$1
echo "Checking CNAME record for $domain..."
echo ""
local cname
cname=$(dig "$domain" +noall +answer -t CNAME | awk '{print $5}')
if [[ -z "$cname" ]]; then
echo " ❌ No CNAME record found"
return 1
fi
if [[ "$cname" == *.github.io. ]] || [[ "$cname" == *.github.io ]]; then
echo " ✅ CNAME points to: $cname"
return 0
else
echo " ❌ CNAME points to: $cname"
echo " ⚠️ Should point to <username>.github.io"
return 1
fi
}
# Function to check CAA records
check_caa_records() {
local domain=$1
echo ""
echo "Checking CAA records for $domain..."
echo ""
local caa_records
caa_records=$(dig "$domain" +noall +answer -t CAA)
if [[ -z "$caa_records" ]]; then
echo " ✅ No CAA records (Let's Encrypt can issue certificates)"
return 0
fi
if echo "$caa_records" | grep -qi "letsencrypt"; then
echo " ✅ CAA allows Let's Encrypt"
return 0
else
echo " ⚠️ CAA records exist but may not allow Let's Encrypt"
echo " Add: 0 issue \"letsencrypt.org\""
return 1
fi
}
# Main checks
if $IS_SUBDOMAIN; then
check_cname_records "$DOMAIN"
else
check_a_records "$DOMAIN"
check_aaaa_records "$DOMAIN"
check_caa_records "$DOMAIN"
# Also check www subdomain
echo ""
echo "========================================="
echo "Checking www subdomain (recommended)..."
echo "========================================="
echo ""
check_cname_records "www.$DOMAIN" || echo " Consider adding www.$DOMAIN as CNAME"
fi
echo ""
echo "========================================="
echo "Summary"
echo "========================================="
echo ""
echo "If DNS is not propagated yet, wait up to 24-48 hours."
echo "Use https://whatsmydns.net to check global propagation."
echo ""
echo "GitHub Pages documentation:"
echo "https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site"
ActionsWorkflow Workflow
Setup custom GitHub Actions workflow for GitHub Pages deployment.
Checklist
- [ ] Identify static site generator
- [ ] Configure GitHub Pages for Actions
- [ ] Create workflow file
- [ ] Configure build settings
- [ ] Deploy and verify
---
When to Use Actions
Use GitHub Actions instead of branch deployment when:
- Using non-Jekyll static site generators (Hugo, Gatsby, Next.js, etc.)
- Need custom build process
- Require specific Node.js/Ruby versions
- Want build caching for faster deploys
- Need to run tests before deployment
---
Step 1: Configure GitHub Pages
1. Go to repository Settings > Pages 2. Under "Build and deployment", select "GitHub Actions" 3. This enables Actions-based deployment
---
Step 2: Create Workflow File
Create .github/workflows/deploy-pages.yml
Generic Static Site
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:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: '.' # Upload entire repository
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4Jekyll Site
name: Deploy Jekyll site to 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:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
- name: Setup Pages
id: pages
uses: actions/configure-pages@v4
- name: Build with Jekyll
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
env:
JEKYLL_ENV: production
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4Next.js (Static Export)
name: Deploy Next.js site to 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:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Setup Pages
uses: actions/configure-pages@v4
with:
static_site_generator: next
- name: Install dependencies
run: npm ci
- name: Build with Next.js
run: npm run build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./out
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4Hugo Site
name: Deploy Hugo site to 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
env:
HUGO_VERSION: 0.121.0
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: ${{ env.HUGO_VERSION }}
extended: true
- name: Setup Pages
id: pages
uses: actions/configure-pages@v4
- name: Build with Hugo
run: |
hugo \
--gc \
--minify \
--baseURL "${{ steps.pages.outputs.base_url }}/"
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./public
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4Astro Site
name: Deploy Astro site to 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:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build with Astro
run: npm run build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./dist
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4---
Step 3: Disable Jekyll (If Not Using)
Create .nojekyll file in repository root:
touch .nojekyll
git add .nojekyll
git commit -m "Disable Jekyll processing"---
Key Actions Reference
| Action | Purpose |
|---|---|
actions/checkout@v4 | Clone repository |
actions/configure-pages@v4 | Configure Pages settings |
actions/upload-pages-artifact@v3 | Package build for deployment |
actions/deploy-pages@v4 | Deploy to GitHub Pages |
---
Configuration Options
configure-pages Options
- uses: actions/configure-pages@v4
with:
# For generators that need base path
static_site_generator: next # or: nuxt, gatsby, sveltekit
# Custom token (if needed)
token: ${{ secrets.GITHUB_TOKEN }}upload-pages-artifact Options
- uses: actions/upload-pages-artifact@v3
with:
# Directory to upload
path: ./build
# Artifact name (default: github-pages)
name: github-pages
# Retention days
retention-days: 1---
Troubleshooting
Deployment Timeout
- Ensure build completes within 10 minutes
- Optimize build process or split into stages
- Check artifact size (max 10GB compressed)
Permission Errors
Ensure workflow has correct permissions:
permissions:
contents: read
pages: write
id-token: writeBase Path Issues
For project sites, configure base path:
- name: Setup Pages
id: pages
uses: actions/configure-pages@v4
- name: Build
run: npm run build
env:
BASE_PATH: ${{ steps.pages.outputs.base_path }}---
Best Practices
1. Use caching - Cache dependencies for faster builds 2. Pin action versions - Use specific versions (@v4 not @latest) 3. Separate build and deploy - Easier debugging 4. Use concurrency - Prevent overlapping deployments 5. Enable workflow_dispatch - Allow manual triggers
CustomDomain Workflow
Configure a custom domain for GitHub Pages.
Checklist
- [ ] Determine domain type (apex vs subdomain)
- [ ] Verify domain ownership
- [ ] Configure DNS records
- [ ] Add custom domain in GitHub
- [ ] Wait for DNS propagation
- [ ] Enable HTTPS
---
Step 1: Determine Domain Type
| Type | Example | Best For |
|---|---|---|
| WWW Subdomain | www.example.com | Most stable, recommended |
| Custom Subdomain | blog.example.com | Project sites |
| Apex Domain | example.com | Brand consistency |
Best Practice: Configure BOTH apex and www subdomain. GitHub auto-redirects between them.
---
Step 2: Verify Domain (Recommended)
Prevent domain takeover by verifying ownership:
1. Go to user/org Settings > Pages 2. Click "Add a domain" 3. Enter your domain 4. Add the TXT record to your DNS 5. Click "Verify"
---
Step 3: Configure DNS Records
For WWW Subdomain (Recommended)
DNS Provider Configuration:
Type: CNAME
Host: www
Value: <username>.github.io
TTL: 3600 (or default)For Custom Subdomain
DNS Provider Configuration:
Type: CNAME
Host: blog (or your subdomain)
Value: <username>.github.io
TTL: 3600 (or default)For Apex Domain
Option A: A Records (IPv4)
Type: A
Host: @ (or blank)
Values:
185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153
TTL: 3600Option B: AAAA Records (IPv6)
Type: AAAA
Host: @ (or blank)
Values:
2606:50c0:8000::153
2606:50c0:8001::153
2606:50c0:8002::153
2606:50c0:8003::153
TTL: 3600Option C: ALIAS/ANAME Record (If supported)
Type: ALIAS or ANAME
Host: @ (or blank)
Value: <username>.github.io
TTL: 3600---
Step 4: Add Custom Domain in GitHub
Via GitHub CLI
# Add custom domain
gh api repos/{owner}/{repo}/pages \
--method PUT \
--field cname='www.example.com'Via Web Interface
1. Go to repository Settings > Pages 2. Under "Custom domain", enter your domain 3. Click "Save"
Note: This creates a CNAME file in your repository (when using branch deploy).
---
Step 5: Verify DNS Configuration
Check A Records
dig example.com +noall +answer -t AExpected output:
example.com. 3600 IN A 185.199.108.153
example.com. 3600 IN A 185.199.109.153
example.com. 3600 IN A 185.199.110.153
example.com. 3600 IN A 185.199.111.153Check CNAME Records
dig www.example.com +nostats +nocomments +nocmdExpected output:
www.example.com. 3600 IN CNAME username.github.io.---
Step 6: Enable HTTPS
1. Wait for DNS to propagate (up to 24 hours) 2. Wait for SSL certificate (up to 1 hour after DNS verified) 3. Go to Settings > Pages 4. Check "Enforce HTTPS"
Certificate Requirements:
- Must have CAA record allowing
letsencrypt.orgOR no CAA records - DNS must point to GitHub Pages IPs
---
Common Issues
DNS Not Propagating
Solution:
- Wait up to 24 hours
- Check for conflicting records
- Remove any wildcard records
HTTPS Not Available
Solution:
- Verify DNS is correctly configured
- Wait up to 1 hour after DNS check passes
- Try removing and re-adding domain
Certificate Errors
Solution:
- Check CAA records don't block Let's Encrypt
- Ensure no conflicting SSL configurations
- Contact domain registrar if CAA issues persist
---
Security Considerations
1. Always verify domain before adding to prevent takeover 2. Never use wildcard DNS (*.example.com) - security risk 3. Update DNS immediately if disabling site 4. Monitor for unauthorized changes to DNS records
---
Reference Commands
# Check current Pages configuration
gh api repos/{owner}/{repo}/pages
# Remove custom domain
gh api repos/{owner}/{repo}/pages \
--method PUT \
--field cname=''
# Check DNS propagation globally
# Use online tools like: whatsmydns.netDeploy Workflow
Deploy changes to GitHub Pages.
Checklist
- [ ] Verify local changes
- [ ] Test locally (optional)
- [ ] Commit and push
- [ ] Monitor deployment
- [ ] Verify live site
---
Quick Deploy
Standard Deployment (Branch-based)
# Stage changes
git add .
# Commit
git commit -m "Update site content"
# Push to trigger deployment
git push origin mainForce Rebuild
# Empty commit to trigger rebuild
git commit --allow-empty -m "Trigger Pages rebuild"
git push---
Pre-Deploy Checklist
1. Verify Changes Locally
# Check what will be committed
git status
git diff --staged
# Review file changes
git diff HEAD2. Test Jekyll Build (If Applicable)
# Install dependencies
bundle install
# Build and serve locally
bundle exec jekyll serve
# Visit http://localhost:40003. Check for Common Issues
# Check for broken links (if using htmlproofer)
bundle exec htmlproofer ./_site
# Validate HTML
bundle exec jekyll build
# Then check _site/ output
# Check for large files
find . -type f -size +10M
# Check for sensitive data
git diff --staged | grep -E "(password|secret|api.?key|token)" -i---
Deployment Methods
Method 1: Push to Branch
When: Publishing source is set to "Deploy from branch"
# Ensure you're on the correct branch
git checkout main # or gh-pages
# Push changes
git push origin mainWhat happens: 1. Push triggers GitHub Pages build 2. Jekyll processes files (unless .nojekyll exists) 3. Site deploys automatically 4. Takes up to 10 minutes
Method 2: GitHub Actions
When: Publishing source is set to "GitHub Actions"
# Push changes to trigger workflow
git push origin main
# Or manually trigger workflow
gh workflow run deploy-pages.ymlWhat happens: 1. Push triggers workflow 2. Workflow runs build steps 3. Artifacts uploaded 4. Deploy action publishes site
Method 3: Manual Trigger
# Trigger workflow manually
gh workflow run deploy-pages.yml --ref main
# Or via API
gh api repos/{owner}/{repo}/actions/workflows/deploy-pages.yml/dispatches \
--method POST \
--field ref=main---
Monitor Deployment
Check Build Status
# For branch deployment
gh api repos/{owner}/{repo}/pages/builds --jq '.[0]'
# For Actions deployment
gh run list --workflow=pages --limit 1
# Watch deployment in real-time
gh run watchView Logs
# List recent runs
gh run list --workflow=pages
# View specific run logs
gh run view <run-id> --log
# View failed step
gh run view <run-id> --log-failed---
Verify Deployment
1. Check Site is Live
# Test site responds
curl -I https://<username>.github.io/<repo>
# Check for correct content
curl -s https://<username>.github.io/<repo> | head -502. Verify Specific Changes
# Check specific page
curl -s https://<username>.github.io/<repo>/new-page/
# Check assets load
curl -I https://<username>.github.io/<repo>/assets/css/style.css3. Clear Cache and Test
# Append cache-busting parameter
curl -s "https://<username>.github.io/<repo>?nocache=$(date +%s)"---
Rollback
Quick Rollback
# Revert last commit
git revert HEAD
git push origin mainRollback to Specific Commit
# Find the commit to rollback to
git log --oneline -10
# Reset to that commit (creates new commit)
git revert --no-commit HEAD~3..HEAD
git commit -m "Rollback to previous version"
git push origin mainEmergency Rollback
# Force push to previous known good state
# WARNING: This rewrites history
git reset --hard <good-commit-sha>
git push --force origin main---
Deploy to Specific Environment
Staging (Preview)
Use pull request previews or separate branch:
# Create staging branch
git checkout -b staging
git push origin staging
# Configure Pages for staging branch
# Or use PR preview feature if availableProduction
# Merge to main for production
git checkout main
git merge staging
git push origin main---
Deployment Best Practices
Before Deploy
1. Test locally - Run bundle exec jekyll serve 2. Check links - Verify internal links work 3. Validate HTML - Ensure valid markup 4. Review diff - Check staged changes 5. No secrets - Verify no sensitive data
During Deploy
1. Monitor build - Watch for errors 2. Set expectations - Up to 10 minutes 3. Don't spam pushes - Rate limit: 10 builds/hour
After Deploy
1. Verify live site - Check changes appear 2. Test critical paths - Navigation, links, forms 3. Check mobile - Responsive design 4. Monitor errors - Check browser console
---
Automation Tips
Deploy on Schedule
# .github/workflows/scheduled-deploy.yml
name: Scheduled Deploy
on:
schedule:
- cron: '0 0 * * *' # Daily at midnight
workflow_dispatch:
jobs:
deploy:
# ... deployment stepsDeploy Only on Tag
on:
push:
tags:
- 'v*'Deploy with Approval
jobs:
deploy:
environment: production # Requires approval if configured---
Quick Reference
| Command | Purpose |
|---|---|
git push origin main | Deploy changes |
gh run list --workflow=pages | Check deployment status |
gh run watch | Monitor deployment live |
curl -I <site-url> | Verify site is up |
git revert HEAD && git push | Rollback last change |
JekyllSetup Workflow
Configure Jekyll for your GitHub Pages site.
Checklist
- [ ] Choose a theme
- [ ] Create _config.yml
- [ ] Setup directory structure
- [ ] Configure front matter
- [ ] Test locally (optional)
- [ ] Deploy and verify
---
Step 1: Choose a Theme
Supported Themes (Zero Configuration)
| Theme | Style |
|---|---|
jekyll-theme-architect | Blueprint/technical |
jekyll-theme-cayman | Clean, modern |
jekyll-theme-dinky | Playful |
jekyll-theme-hacker | Terminal/hacker |
jekyll-theme-leap-day | Yellow notebook |
jekyll-theme-merlot | Warm, inviting |
jekyll-theme-midnight | Dark, sophisticated |
jekyll-theme-minima | Minimal blog |
jekyll-theme-minimal | Simple, clean |
jekyll-theme-modernist | Modern, sleek |
jekyll-theme-slate | Gray, professional |
jekyll-theme-tactile | Textured |
jekyll-theme-time-machine | Retro futuristic |
Remote Themes (Any GitHub Jekyll Theme)
Use remote_theme to use themes not in the supported list.
---
Step 2: Create _config.yml
Minimal Configuration
theme: jekyll-theme-minimal
title: My Site
description: A description of my siteFull Configuration
# Theme
theme: jekyll-theme-minimal
# Or use remote theme:
# remote_theme: pages-themes/minimal@v0.2.0
# Site Settings
title: My Site
description: A description of my site
url: https://username.github.io
baseurl: "" # or "/repository-name" for project sites
# Author
author:
name: Your Name
email: your@email.com
# Build Settings
markdown: kramdown
highlighter: rouge
# Plugins (optional)
plugins:
- jekyll-feed
- jekyll-seo-tag
- jekyll-sitemap
# Exclude from build
exclude:
- README.md
- Gemfile
- Gemfile.lock
- node_modules
- vendor---
Step 3: Directory Structure
Basic Structure
.
├── _config.yml # Site configuration
├── index.md # Homepage
├── about.md # About page
├── _posts/ # Blog posts
│ └── 2024-01-01-welcome.md
├── assets/
│ ├── css/
│ │ └── style.scss # Custom styles
│ └── images/
└── _layouts/ # Custom layouts (optional)
└── default.htmlBlog Post Naming Convention
_posts/YYYY-MM-DD-title.mdExample: _posts/2024-01-15-my-first-post.md
---
Step 4: Configure Front Matter
Page Front Matter
---
layout: default
title: Page Title
description: Page description for SEO
permalink: /custom-url/
---
Page content here...Blog Post Front Matter
---
layout: post
title: "Blog Post Title"
date: 2024-01-15 10:00:00 -0500
categories: [category1, category2]
tags: [tag1, tag2]
author: Your Name
---
Post content here...---
Step 5: Customize Theme
Custom CSS
Create assets/css/style.scss:
---
---
@import "{{ site.theme }}";
// Custom styles below
body {
font-family: 'Your Font', sans-serif;
}
h1, h2, h3 {
color: #333;
}
// Add more custom styles...Custom Layout
1. Find theme's _layouts/default.html in theme repository 2. Copy contents to your _layouts/default.html 3. Modify as needed
Example custom layout:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ page.title }} | {{ site.title }}</title>
<link rel="stylesheet" href="{{ '/assets/css/style.css' | relative_url }}">
</head>
<body>
<header>
<h1>{{ site.title }}</h1>
<nav>
<a href="{{ '/' | relative_url }}">Home</a>
<a href="{{ '/about' | relative_url }}">About</a>
</nav>
</header>
<main>
{{ content }}
</main>
<footer>
<p>© {{ site.time | date: '%Y' }} {{ site.author.name }}</p>
</footer>
</body>
</html>---
Step 6: Test Locally (Optional)
Prerequisites
# Install Ruby (if not installed)
# macOS: brew install ruby
# Ubuntu: sudo apt install ruby-full
# Install Bundler
gem install bundlerSetup
Create Gemfile:
source "https://rubygems.org"
gem "github-pages", group: :jekyll_plugins
# Optional plugins
gem "jekyll-feed"
gem "jekyll-seo-tag"Run Locally
# Install dependencies
bundle install
# Ruby 3.0+ may need:
bundle add webrick
# Start local server
bundle exec jekyll serve
# Or with live reload
bundle exec jekyll serve --livereload
# Access at http://localhost:4000---
Step 7: Deploy
# Commit changes
git add .
git commit -m "Setup Jekyll site"
git push origin mainWait up to 10 minutes for deployment.
---
Common Issues
Theme Not Applying
- Verify theme name in
_config.ymlis correct - Clear browser cache
- Check for YAML syntax errors
Custom CSS Not Loading
- Ensure front matter dashes
---at top of SCSS file - Verify @import statement matches theme name
- Check file is in correct location
Build Errors
- Check Actions tab for error messages
- Verify YAML front matter syntax
- Ensure all plugins are supported
---
Useful Liquid Tags
<!-- Link to another page -->
[About]({{ '/about' | relative_url }})
<!-- Include image -->

<!-- Loop through posts -->
{% for post in site.posts %}
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
<p>{{ post.excerpt }}</p>
{% endfor %}
<!-- Site metadata -->
{{ site.github.repository_name }}
{{ site.github.project_title }}---
Resources
QuickStart Workflow
Setup a new GitHub Pages site from scratch.
Checklist
- [ ] Determine site type (user/org vs project)
- [ ] Verify repository name requirements
- [ ] Choose publishing source
- [ ] Create initial content
- [ ] Enable GitHub Pages
- [ ] Verify deployment
---
Step 1: Determine Site Type
Ask the user:
- Is this for a personal/organization homepage? → User/Org Site
- Is this for a specific project? → Project Site
User/Organization Site
Requirements:
Repository name: <username>.github.io
URL: https://<username>.github.io
Limit: ONE per accountCreate repository:
# Via GitHub CLI
gh repo create <username>.github.io --public --description "My personal site"
# Or via web interface at github.com/newProject Site
Requirements:
Repository name: any-name
URL: https://<username>.github.io/<repo-name>
Limit: One per repository---
Step 2: Choose Publishing Source
Option A: Deploy from Branch (Recommended for Jekyll)
1. Create content in repository root or /docs folder 2. Go to Settings > Pages 3. Select "Deploy from a branch" 4. Choose branch and folder
Supported folders:
/(root) - Most common/docs- Useful for project documentation
Option B: GitHub Actions (Custom Builds)
1. Go to Settings > Pages 2. Select "GitHub Actions" 3. Create workflow file (see ActionsWorkflow.md)
---
Step 3: Create Initial Content
Minimal Setup
Create index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Site</title>
</head>
<body>
<h1>Welcome to My Site</h1>
<p>This site is hosted on GitHub Pages.</p>
</body>
</html>Jekyll Setup (Markdown)
Create index.md:
---
layout: default
title: Home
---
# Welcome to My Site
This site is hosted on GitHub Pages.Create _config.yml:
theme: jekyll-theme-minimal
title: My Site
description: A GitHub Pages site---
Step 4: Enable GitHub Pages
# Via GitHub CLI
gh api repos/{owner}/{repo}/pages \
--method POST \
--field source='{"branch":"main","path":"/"}'
# Or manually via Settings > Pages---
Step 5: Verify Deployment
1. Check deployment status in Actions tab 2. Wait up to 10 minutes for initial deployment 3. Visit your site URL
Verification commands:
# Check if site is responding
curl -I https://<username>.github.io
# Check deployment status via API
gh api repos/{owner}/{repo}/pages---
Troubleshooting
Site not appearing:
- Verify Pages is enabled in Settings
- Check Actions tab for build errors
- Ensure entry file exists (index.html, index.md, or README.md)
404 Error:
- Check publishing source configuration
- Verify files are in correct folder
- Wait a few minutes and clear browser cache
---
Next Steps
- Add custom domain (CustomDomain.md)
- Configure Jekyll theme (JekyllSetup.md)
- Setup custom build process (ActionsWorkflow.md)
Troubleshoot Workflow
Diagnose and fix common GitHub Pages issues.
Checklist
- [ ] Identify the symptom
- [ ] Check build status
- [ ] Verify configuration
- [ ] Apply fix
- [ ] Confirm resolution
---
Quick Diagnosis
Check Build Status
# Via GitHub CLI
gh api repos/{owner}/{repo}/pages
# Check recent deployments
gh api repos/{owner}/{repo}/deployments --jq '.[0:5]'
# View workflow runs (if using Actions)
gh run list --workflow=pagesCheck Site Status
# Test if site responds
curl -I https://<username>.github.io/<repo>
# Check for redirects
curl -ILs https://<username>.github.io/<repo> | grep -i location---
Common Issues
Issue: Site Not Publishing
Symptoms:
- No site at expected URL
- 404 error on site URL
Diagnosis: 1. Is Pages enabled? Check Settings > Pages 2. Is there an entry file? (index.html, index.md, or README.md) 3. Is the publishing source correct? 4. Are there build errors?
Solutions:
# Verify Pages is enabled
gh api repos/{owner}/{repo}/pages
# Check if entry file exists
ls -la index.html index.md README.md 2>/dev/null
# Check publishing source settings
gh api repos/{owner}/{repo}/pages --jq '.source'---
Issue: 404 Error
Symptoms:
- Site shows GitHub 404 page
- Only some pages return 404
Diagnosis: 1. Entry file at correct location? 2. Correct base URL for links? 3. Case-sensitive file names? 4. Jekyll build excluding files?
Solutions:
For root path 404:
# Ensure entry file exists at publishing source
# For root: ./index.html or ./index.md
# For /docs: ./docs/index.html or ./docs/index.mdFor broken links:
# In _config.yml for project sites
baseurl: "/repository-name"For Jekyll excluding files:
# In _config.yml, check include/exclude settings
include:
- _pages
- important-file.md---
Issue: Custom Domain Not Working
Symptoms:
- Domain shows error or wrong site
- HTTPS not available
- Domain verification failed
Diagnosis: 1. DNS records correct? 2. CNAME file present? 3. DNS propagated? 4. Domain verified?
Solutions:
Check DNS:
# For apex domain
dig example.com +noall +answer -t A
# Expected IPs:
# 185.199.108.153
# 185.199.109.153
# 185.199.110.153
# 185.199.111.153
# For subdomain
dig www.example.com +noall +answer -t CNAME
# Expected: username.github.ioFix CNAME file:
# CNAME file must be uppercase
# Contains only the domain, nothing else
echo "www.example.com" > CNAME
git add CNAME && git commit -m "Add CNAME" && git pushWait for propagation:
- DNS changes take up to 24 hours
- HTTPS takes up to 1 hour after DNS verification
---
Issue: HTTPS Not Available
Symptoms:
- "Enforce HTTPS" checkbox disabled
- Certificate errors
- Mixed content warnings
Diagnosis: 1. DNS fully propagated? 2. CAA records blocking? 3. Domain recently changed?
Solutions:
Wait and retry:
# Remove and re-add custom domain to trigger new certificate
gh api repos/{owner}/{repo}/pages \
--method PUT --field cname=''
# Wait a minute, then re-add
gh api repos/{owner}/{repo}/pages \
--method PUT --field cname='www.example.com'Check CAA records:
dig example.com CAA
# Must include or not block: letsencrypt.orgFix mixed content:
# Search for http:// in your files
grep -r "http://" --include="*.html" --include="*.md" --include="*.scss"
# Replace with https:// or protocol-relative //---
Issue: Build Failures
Symptoms:
- Deployment never completes
- Error emails from GitHub
- Build status shows failure
Diagnosis: 1. Check Actions tab for logs 2. Check email for error message 3. Test local build
Solutions:
View build logs:
# List recent workflow runs
gh run list --workflow=pages --limit 5
# View specific run
gh run view <run-id> --logCommon build errors:
Liquid syntax error:
# Escape Liquid tags in code blocks
{% raw %}
{{ variable }}
{% endraw %}Invalid YAML:
# Check for:
# - Inconsistent indentation
# - Missing quotes around special characters
# - Tab characters (use spaces)Missing dependencies:
# In Gemfile, ensure github-pages gem is included
source "https://rubygems.org"
gem "github-pages", group: :jekyll_plugins---
Issue: Changes Not Appearing
Symptoms:
- Pushed changes not visible
- Old content still showing
- Cache issues
Diagnosis: 1. Was push successful? 2. Did build complete? 3. Browser cache? 4. CDN cache?
Solutions:
Verify push and build:
# Check last commit
git log -1
# Check if deployed
gh api repos/{owner}/{repo}/pages/builds --jq '.[0]'Clear caches:
# Force browser refresh
# Chrome/Firefox: Ctrl+Shift+R (Cmd+Shift+R on Mac)
# Or append query string
# https://site.github.io/?v=2Force rebuild:
# Empty commit to trigger rebuild
git commit --allow-empty -m "Trigger rebuild"
git push---
Issue: Theme Not Applying
Symptoms:
- Site shows unstyled content
- Wrong theme appearing
- Custom CSS not loading
Diagnosis: 1. Theme name correct in _config.yml? 2. Theme supported by GitHub Pages? 3. Custom CSS file has front matter?
Solutions:
Verify theme configuration:
# _config.yml - use exact theme name
theme: jekyll-theme-minimal
# Or for remote themes
remote_theme: pages-themes/minimal@v0.2.0Fix custom CSS:
/* assets/css/style.scss - MUST have front matter */
---
---
@import "{{ site.theme }}";
/* Custom styles below */---
Diagnostic Commands Reference
# Check Pages configuration
gh api repos/{owner}/{repo}/pages
# Check build status
gh api repos/{owner}/{repo}/pages/builds
# View deployment history
gh api repos/{owner}/{repo}/deployments
# Test site response
curl -IL https://<username>.github.io/<repo>
# Check DNS records
dig <domain> A
dig <domain> AAAA
dig www.<domain> CNAME
# View workflow logs
gh run list --workflow=pages
gh run view <run-id> --log
# Test local Jekyll build
bundle exec jekyll build --verbose---
When to Contact GitHub Support
Contact support if:
- Build failures with no clear error
- DNS correctly configured but domain not working after 48 hours
- HTTPS not available after 24 hours despite correct setup
- Account-specific Pages issues
Before contacting: 1. Document all troubleshooting steps taken 2. Include repository name and URLs 3. Provide dig output for DNS issues 4. Include build log excerpts for build failures