
Getting Started with Cloudflare and GitHub Pages
March 10, 2022
·8 min
|Cloudflare sits in front of your origin server as a reverse proxy and content delivery network. When a visitor types your domain into a browser, the DNS query resolves to a Cloudflare edge node rather than your actual server IP. That edge node handles the TLS handshake, inspects the request, serves cached content if available, and only forwards the request to your origin when it cannot serve it from cache. This model means the browser connects to a data centre that is geographically close, and your real server IP is never exposed to the public internet.
Cloudflare operates more than 300 data centres spread across every continent. Requests are routed using Anycast: all of those edge nodes advertise the same set of IP addresses. BGP routing naturally delivers each request to whichever node is topologically closest to the client. You do not configure this routing yourself. It is built into how the network is set up.
How the Proxy and Cache Layer Works
When you create a DNS record in Cloudflare and enable the orange cloud icon (proxy mode), Cloudflare replaces your origin IP with its own addresses in DNS responses. Requests hit the edge, which applies your firewall rules, rate limiting rules, and cache rules before deciding what to do. For static assets like images, CSS, and JavaScript files, Cloudflare follows the Cache-Control headers your server sends. If the origin says max-age=86400, the edge caches that response for 24 hours and does not touch your server for that file again until the TTL expires.
You can also set a custom cache TTL from the Cloudflare dashboard or override caching behaviour with Cache Rules. A Cache Rule lets you match URL patterns and decide whether to bypass cache, force cache, or set a specific edge TTL independent of what the origin says. For example, you might want to cache all paths starting with /assets/ for 30 days at the edge even if your origin only sets a 1-hour TTL.
Cloudflare's free plan covers the basics: full proxy, DDoS protection at layers 3, 4, and 7, universal SSL, and a CDN across the full edge network. You do not need a paid plan to get meaningful performance and security improvements for a personal or small business site.
Setting Up a Static Site on GitHub Pages
GitHub Pages serves static files directly from a GitHub repository. You push HTML, CSS, and JavaScript to a branch and GitHub builds and serves it at a URL like username.github.io/repo-name. There is no server-side code execution, no database, and no build step unless you use Jekyll or GitHub Actions. For a plain HTML site the simplest setup is a repository with an index.html at the root and GitHub Pages enabled on the main branch.
Your repository structure for a basic static site might look like this:
my-site/
index.html
about.html
assets/
css/
main.css
js/
app.js
images/
hero.webp
CNAMEThe CNAME file contains a single line: your custom domain. If you skip this file, GitHub Pages only serves the site at the default github.io URL and custom domain routing will not work correctly. Create it with your domain exactly as it will appear in DNS:
yourdomain.comTo enable GitHub Pages, go to your repository on GitHub, open Settings, scroll to Pages, choose the branch and folder (usually main and root /), and save. GitHub will publish your site within a minute or two and show you the URL.
Pointing Your Domain to GitHub Pages Through Cloudflare
Add your domain to Cloudflare by creating a free account, clicking Add a Site, and following the wizard. Cloudflare scans your existing DNS records and imports them. You then update your domain registrar's nameservers to the two Cloudflare nameservers that are assigned to your account. Once propagation completes (usually under 30 minutes), all DNS for that domain is managed through Cloudflare.
For a GitHub Pages site you need two types of DNS records. If you are using the apex domain (no www prefix), GitHub requires four A records pointing to their servers. If you are using a subdomain like www, a single CNAMErecord works. Add these in Cloudflare's DNS panel:
# Apex domain A records (GitHub Pages IPs)
Type Name Value Proxy status
A @ 185.199.108.153 Proxied
A @ 185.199.109.153 Proxied
A @ 185.199.110.153 Proxied
A @ 185.199.111.153 Proxied
# www subdomain CNAME
Type Name Value Proxy status
CNAME www username.github.io ProxiedSetting these records to Proxied (orange cloud) means traffic flows through Cloudflare's edge. Setting them to DNS only (grey cloud) means Cloudflare just provides DNS and traffic goes directly to GitHub's servers without any Cloudflare features applied.
When you proxy apex A records through Cloudflare, your origin IP (GitHub's servers) is hidden from DNS lookups. A dig yourdomain.comwill return Cloudflare's IP addresses, not GitHub's. This is the intended behaviour and does not break anything.SSL/TLS Configuration
Cloudflare handles two separate TLS connections. One is between the visitor's browser and Cloudflare's edge. The other is between Cloudflare's edge and your origin server (GitHub Pages in this case). You control how the edge-to-origin connection works through the SSL/TLS mode setting in your Cloudflare dashboard.
GitHub Pages supports HTTPS natively when you add a custom domain. Once you add the CNAME file and configure the DNS records, GitHub will provision a Let's Encrypt certificate for your domain automatically. With that in place, set Cloudflare's SSL/TLS mode to Full (strict). This means:
Visitor <--HTTPS--> Cloudflare Edge <--HTTPS--> GitHub Pages (valid cert)
SSL/TLS Mode: Full (strict)
- Encrypts both legs of the connection
- Validates the origin certificate (Let's Encrypt cert from GitHub)
- Prevents Cloudflare from connecting to origin over plain HTTPAvoid using Flexible mode, which encrypts only the visitor-to-Cloudflare leg and sends requests to your origin over plain HTTP. For GitHub Pages specifically this causes redirect loops because GitHub forces HTTPS on all requests once you have it enabled.
Adding Response Headers With a _headers File
GitHub Pages does not let you configure HTTP response headers through a server config file the way you would with Apache or nginx. However, Cloudflare lets you inject headers at the edge using Transform Rules. Alternatively, if you move to Cloudflare Pages (described below), you can use a _headers file in your repository root to define headers declaratively:
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' https://fonts.googleapis.com; font-src https://fonts.gstatic.com
/assets/*
Cache-Control: public, max-age=2592000, immutableThe /* pattern applies headers to all responses. The /assets/* block overrides the cache header specifically for static assets and sets a 30-day max-age with the immutable directive, telling browsers they do not need to revalidate those files during that period.
Moving to Cloudflare Pages
Cloudflare Pages is Cloudflare's own static hosting platform and an alternative to GitHub Pages. It connects directly to your GitHub repository, builds on push, and deploys to the same global edge network. The advantage over GitHub Pages plus Cloudflare proxy is that you get the _headers file support, deploy previews for pull requests, and build pipeline integration without any extra configuration.
To set it up, go to the Cloudflare dashboard, open Pages, click Create a project, connect your GitHub account, select your repository, and configure the build settings. For a plain HTML site with no build step, leave the build command blank and set the output directory to / (root):
Project name: my-site
Production branch: main
Build command: (leave empty for plain HTML)
Build output: /
Environment variables: (none needed for plain HTML)After clicking Save and Deploy, Cloudflare Pages clones your repository, copies the files to its edge network, and serves them at your-project.pages.dev. Every push to the main branch triggers a new deployment automatically. Pushes to other branches or pull requests get a unique preview URL so you can review changes before merging.
Custom Domain on Cloudflare Pages
Once your Cloudflare Pages project is deployed, you can attach a custom domain from within the Pages project settings. Go to the Custom domains tab, click Set up a custom domain, and enter your domain. Because your domain is already managed by Cloudflare DNS, it will automatically create the required CNAME record:
Type Name Value Proxy status
CNAME @ my-site.pages.dev DNS only (managed by Pages)
CNAME www my-site.pages.dev DNS only (managed by Pages)Cloudflare Pages manages its own TLS certificate for your custom domain so you do not need to configure SSL mode separately. The full HTTPS chain from visitor to edge to origin is handled internally by the platform.
Verifying the Setup
After everything is configured, you can verify the connection chain using curl with the -I flag to inspect response headers. Look for the cf-rayheader, which is present on every response that flows through Cloudflare's edge:
curl -I https://yourdomain.com
HTTP/2 200
date: Thu, 10 Mar 2022 10:00:00 GMT
content-type: text/html; charset=utf-8
server: cloudflare
cf-ray: 8a1b2c3d4e5f6789-SYD
cache-control: public, max-age=0, must-revalidate
x-frame-options: DENY
x-content-type-options: nosniffThe cf-ray header confirms the request hit a Cloudflare edge node. The airport code at the end of the ray ID (SYD in this example) identifies which data centre handled the request. If you do not see cf-ray, the request is bypassing Cloudflare, which means the DNS records are set to grey cloud (DNS only) rather than orange cloud (proxied).