Host a website for free with GitHub and Cloudflare, step by step

How to take a website from a folder of files to a live address with your own domain, HTTPS, free email forwarding and visitor statistics. Design, repository, Cloudflare deployment, domain, launch checklist and measuring, with the mistakes to avoid.

How does a folder of text files on your computer become a website anyone in the world can open? You save the files in a repository, a hosting service builds them whenever you push a change, and a domain name points visitors to the result. This guide walks through every step with free tools: GitHub for the files, Cloudflare for hosting, HTTPS, email forwarding and statistics, and one domain name that costs about the price of a lunch per year.

Edit a file  →  git push  →  GitHub  →  Cloudflare builds the site  →  yourdomain.com

An analogy. The domain is the street address, the repository is the building plans, and Cloudflare is the builder and delivery service. You change the plans, and the builder rebuilds the house and delivers it to the same address.

Everything here is a static site: pages that are generated once and then served as plain files. It is fast, safe, and almost free to run. Replace yourdomain.com and my-site with your own names throughout.

What it costs

Item Cost Notes
GitHub repository Free Public or private both work
Cloudflare hosting Free Includes HTTPS and a free address ending in .pages.dev or .workers.dev
Domain name About 10 to 12 USD per year The only cost you cannot avoid
Email forwarding on your domain Free Cloudflare Email Routing
Visitor statistics Free Cloudflare analytics and Google Search Console

Free plans have limits, such as a monthly number of builds and a maximum file size. They are generous for a personal site, but check the current numbers on Cloudflare’s pricing page before you plan something large.

1. Design it simple first

A good site is decided before any code. Spend ten minutes answering four questions.

  1. Who is it for? One visitor type is easier to serve than five.
  2. What should they do? Read, download, contact you. Every page should lead to one of these.
  3. Which pages do you need? Start small.
  4. What is the first screen? Your name or topic, one sentence, one button.

A small page plan

Page Purpose Contains
Home First impression Who you are, one sentence, main button, three highlights
About Trust Short story, skills, a way to contact you
Work or Projects Proof Cards with a title, one line and a link
Blog Search traffic Tutorials that answer real questions
Contact Action Email address or a simple form

Design rules that keep it professional

  • One stylesheet, two or three colours, two fonts. Fewer choices look more confident.
  • Readable text. Body text around 17 to 19 px, lines no wider than about 70 characters, strong contrast.
  • Mobile first. More than half of visitors will arrive on a phone, so design the narrow screen first.
  • No third-party scripts. Ads, trackers and external fonts slow the page and raise privacy questions. Host your own fonts and images.
  • Light and dark theme if you can, using the visitor’s system setting.
  • Descriptive addresses. /blog/my-first-post/ beats /post1.html, for people and for search engines.
  • Small images. Use WebP or compressed JPG, and short looping videos instead of heavy GIFs.

Keep content separate from design

The most useful decision is to store your content as plain files and let a small generator turn them into pages. Then you edit text, never HTML.

my-site/
├── build.py              # the generator (or any static site tool)
├── requirements.txt      # packages the generator needs
├── content/
│   ├── site.yml          # name, links, home page text
│   └── blog/             # one Markdown file per post
├── templates/            # page layouts (header, footer, ...)
├── static/               # css, js, images, fonts, files to download
└── _site/                # generated output, never committed

You can use a ready-made tool instead of writing your own, such as Hugo, Jekyll, Eleventy or Astro. Cloudflare builds all of them. The steps below only assume that one command produces a folder of finished pages.

2. Prepare the repository

Create it

  1. Create an account on GitHub and make a new repository, for example my-site. Private is fine, because Cloudflare reads it through an app you authorise.
  2. Put your files in it and commit.
Shell
git init
git add -A
git commit -m "First version"
git branch -M main
git remote add origin https://github.com/YOUR-NAME/my-site.git
git push -u origin main

Do not commit the generated folder

Add the output folder to .gitignore, so the repository holds only the sources:

_site/
__pycache__/

Test the build on your computer first

A build that works locally almost always works on Cloudflare.

Shell
pip install -r requirements.txt
python3 build.py

If it prints an error, fix it now. Reading the same error in a remote build log is slower.

Two checks that save hours

Check Why
Use spaces, never tabs, in YAML files One invisible tab character stops the whole build. The error says found character '\t' that cannot start any token.
Never store passwords or keys in the repository Git remembers every old version. A secret you delete later is still in the history. Use environment variables or the dashboard’s secret settings.

3. Deploy on Cloudflare for free

Create the project

  1. Create a free Cloudflare account.
  2. Open Workers & Pages and choose Create application.
  3. Choose to import an existing Git repository and authorise GitHub. Pick my-site, and give access to that one repository only.
  4. Name the project. Its free address will be my-site.pages.dev or my-site.workers.dev, depending on which product the dashboard sets up.

Cloudflare has two ways to host a static site: Pages, and Workers with static assets. Both are free for this use, and the dashboard may steer you to either one. The settings are almost identical. Where they differ, both are shown.

Fill in the build settings

Setting Value Meaning
Production branch Your main branch, for example main Pushes to this branch go live
Build command python build.py The one command that creates your pages
Output directory (Pages) _site The folder to publish
Deploy command (Workers) npx wrangler deploy Publishes the output folder
Root directory / Where your files start
Environment variables None for a simple site Add secrets here, never in the repository

If your repository has a requirements.txt, Cloudflare installs those packages by itself before the build command runs. You do not need to put pip install in the build command.

Workers only: add one small config file

A Worker needs to know which folder to publish. Create a file named wrangler.jsonc in the root of the repository, next to build.py:

json
{
  "name": "my-site",
  "compatibility_date": "2026-10-01",
  "assets": {
    "directory": "./_site",
    "not_found_handling": "404-page"
  }
}
  • name must match the project name in the dashboard.
  • directory is your output folder.
  • 404-page shows your own 404 page for addresses that do not exist.
  • Keep only one Wrangler config file. A second one, such as wrangler.toml, can conflict.

Pages projects do not need this file.

Read the build log

Every push starts a build. Open Deployments and read the log. A healthy build moves through five stages.

Stage What happens If it is missing or fails
Initializing A clean build machine starts Usually a temporary Cloudflare problem. Retry.
Cloning Your repository is downloaded Check the GitHub authorisation and the branch name
Installing requirements.txt packages are installed Check the package names in requirements.txt
Building Your build command runs Read the error under it. This is where most problems are.
Deploying The finished folder goes live Check the output directory or wrangler.jsonc

If the Building stage never appears, the build command is empty. If the build fails, the previous version stays online, so a mistake never takes your site down.

When all stages are green, open your free address. You now have a live site with HTTPS and no cost.

4. Add your own domain

Choose the name

  • Prefer .com. It is what people type by default and it looks the most trustworthy.
  • Use .org only for a community or non-profit project.
  • Your own name often works better for search than a brand name. You can register both and redirect one to the other.
  • Short, easy to spell, no hyphens.

Register it

The simplest route is Cloudflare Registrar, because everything is in one dashboard: Domain Registration > Register Domains, search for the name and check out. Cloudflare sells domains at its own cost without a markup, and the price includes privacy protection for your contact details.

If you buy from another registrar, add the domain to Cloudflare as a site and change the nameservers at the registrar to the two Cloudflare gives you. Propagation can take from a few minutes to a day.

Turn on auto-renew. An expired domain breaks every link you ever shared.

Connect it to the project

  1. Open your project, then Custom domains (Pages) or Domains & Routes (Workers).
  2. Add yourdomain.com.
  3. Add www.yourdomain.com too.
  4. Wait until both show Active. DNS records and the HTTPS certificate are created for you.

If the dashboard says the domain is already in use, it is attached to an older project or Worker. Remove it there first, then add it again.

Send www to the bare address

Two addresses for the same page split your search ranking. Pick one, usually the bare yourdomain.com, and redirect the other.

In the dashboard, open your domain, then Rules > Redirect Rules, and create:

Field Value
When the address matches www.yourdomain.com/*
Redirect to https://yourdomain.com/${1}
Status code 301 (permanent)

Tell the site its new address

Your generator should know the official address, because it writes canonical links, the sitemap and the feed from it. Change it once, in one place:

YAML
url: https://yourdomain.com

Then search the project for any old copies of the free address, including in documents such as your CV or README:

Shell
grep -rn "my-site.pages.dev" . --exclude-dir=.git --exclude-dir=_site

Replace every match, commit and push. Wait for the build, then open the site and do a hard refresh (Ctrl+Shift+R) in case your browser kept the old version.

Check the security setting

In SSL/TLS, the encryption mode should be Full or Full (strict) for a site hosted on Cloudflare. A visitor should see the padlock on both yourdomain.com and www.yourdomain.com.

5. Optional: free email on your domain

A professional address such as contact@yourdomain.com costs nothing with Email Routing. It forwards mail to an inbox you already use. It is a forwarding service, not a separate mailbox.

  1. In the dashboard, open your domain, then Email > Email Routing, and enable it. Let Cloudflare add its mail records.
  2. Under Destination addresses, add the inbox you want to receive mail in, and click the verification link Cloudflare sends there.
  3. Under Routing rules, create a custom address. Type only the part before the @, choose Send to an email, and pick the destination. The Save button stays grey until the pattern, the action and the destination are all filled in.
  4. Test it from a different account, not from the destination inbox itself.

Keep the catch-all rule on Drop. Otherwise every guessed address collects spam.

Replying from the new address needs an extra step in your mail service, often called Send mail as, and a matching SPF record so the mail does not land in spam. Skip it at first. Receiving is enough for most sites.

6. Launch checklist

Before you tell anyone, walk through this list on a phone and on a computer.

Check Done
Home page loads on https://yourdomain.com with a padlock ☐
www.yourdomain.com redirects to the bare address ☐
Every menu link works, and the 404 page looks right ☐
Images, fonts and downloads load ☐
Each page has its own title and description ☐
The site looks right in dark mode and on a narrow screen ☐
The contact form or email address works ☐
https://yourdomain.com/sitemap.xml opens ☐
https://yourdomain.com/robots.txt opens ☐
No old free-address links remain ☐
Social links and your own profiles point to the new domain ☐

Then update the website link on your social profiles, your email signature and any documents that carry it. One address everywhere builds trust and search ranking.

7. Your daily workflow

Once it is set up, publishing is three steps.

Shell
python3 build.py --serve        # preview at http://localhost:8000
git add -A
git commit -m "Add a new post"
git push                        # Cloudflare builds and publishes
  • Preview first. Look at the change locally before you push.
  • One change at a time. If a build fails, you know which change caused it.
  • A failed build is harmless. The previous version stays online until a build succeeds.
  • Go back in time. Every deployment is listed under Deployments, and older ones can be restored if you need to.
  • Editing on GitHub works too. Open a file, click the pencil, and commit. It triggers the same build. Pull the change to your computer afterwards.

8. Measure your visitors

Cloudflare traffic analytics

Open your domain, then Analytics & Logs > Traffic, and pick a time range. You get requests, visitors, countries and popular pages. The numbers are counted on Cloudflare’s servers, so your pages need no extra script and the privacy of your visitors stays intact.

Hosting-level numbers are also available on the project itself, under Metrics.

Google Search Console

This shows how people find you on Google. It is the most useful free tool for a blog.

  1. Open Search Console and choose Add property > Domain.
  2. Enter yourdomain.com. Google asks you to add a TXT record to your DNS. With Cloudflare as your DNS provider, this takes one click or one copy and paste.
  3. After verification, open Sitemaps and submit https://yourdomain.com/sitemap.xml.
  4. Wait a few days. Then check Performance for searches, clicks and the pages that appear.

What each tool tells you

Question Tool
How many people visited this week? Cloudflare Traffic
Which countries do they come from? Cloudflare Traffic
Which searches bring them here? Search Console
Which pages does Google show? Search Console
Is the site up and error-free? Project Metrics
How long do people stay, and what do they click? Needs a page-level analytics script (see below)

Read the numbers honestly

  • Requests are not people. One page view triggers many requests for style sheets, fonts and images.
  • Bots count too. Crawlers and scanners inflate visitor numbers, especially on a new site.
  • Small numbers are normal at first. A blog grows slowly. Compare this month with last month, not with a famous site.

Optional: Cloudflare Web Analytics

For counts of real page views, you can enable Web Analytics. It uses no cookies, but it works by loading a small Cloudflare script into your pages. If your footer promises “no third-party requests”, change the wording when you turn it on, for example to “No cookies, no ad trackers.”

Common problems and fixes

Problem Fix
The build fails after 0 seconds with no message Open the build page and read the full log, then retry. If the log is empty, push a small new commit.
The log goes from Installing straight to Deploying The build command is empty. Set it to your generator’s command.
found character '\t' that cannot start any token A tab in a YAML file. Retype the indentation and the gap after the colon with spaces.
Deploy fails, “nothing to deploy” or “directory not found” The output folder was never created, or the name is wrong. Check the build command, the output directory and wrangler.jsonc.
The site shows the old design The branch is wrong, or the build failed. Check the production branch and the latest build. Then hard refresh.
The domain stays pending Wait a few minutes. Check that no older project holds the domain and that the DNS records exist.
www does not redirect Create the Redirect Rule and make sure the www DNS record is proxied (orange cloud).
The padlock shows a warning Wait for the certificate to be issued, and set SSL/TLS to Full.
Email Routing says “Syncing” for a long time Wait up to 30 minutes, then use the button that adds or fixes the records.
The Save button for a routing rule is grey Fill in the pattern, the action and a verified destination.
A form on the site stopped delivering after you changed its address Form services usually ask you to confirm a new address once. Send a test and click the confirmation link.
A document in the repository still shows the old address Search the whole repository for the old address, including documents and templates.

Security and privacy habits

  • Never commit passwords, API tokens or private keys. If you did, change them, because deleting the file does not remove them from the history.
  • Do not build an admin panel with a password in the page. A static site needs none, because you edit files in Git.
  • Give Cloudflare access to one repository, not your whole GitHub account.
  • Turn on two-factor authentication on GitHub, Cloudflare and your email.
  • Keep the registrar contact details private, and keep your recovery email current.

Further reading

Try it yourself

  1. Make a repository with one index.html that says “Hello”, and publish it on the free Cloudflare address.
  2. Break the build on purpose, for example with a typo in the build command, and read what the log says. Then fix it and watch the previous version stay online meanwhile.
  3. Register a cheap domain, connect it, and make www redirect to the bare address.
  4. Create contact@yourdomain.com, forward it to your inbox, and send yourself a test.
  5. Submit your sitemap to Search Console, and check back after one week to see your first search impressions.

Basheer Al-Tawil is a PhD researcher in Robotics and AI at Otto von Guericke University. Found a mistake or have a question? Write to me.

Browse by topic