Give a Static Website a Custom Domain and File Index
You have a folder of files (slides, PDFs, ZIP archives, a few images). You want the files at an address like files.example.com, with a front page that lists all of them. A static host (a service that serves plain files) serves the files, but it expects you to supply that front page. GitHub Pages looks for an index.html, index.md or README.md as the entry file (the page that opens first). On Cloudflare Pages, if a project has no 404.html, a path that matches no file gets the root page instead. So a folder without its own index does not show its contents.
So the job has three parts: build the index page, deploy the folder, and connect the domain.
1. Clean up the folder
Each file that you deploy gets its own public URL, even if the index does not link to it. Before you build the index, remove working notes, drafts, private files, and any other file that people must not download. If you leave a file out of the index, people can still open it.
A simple layout works best:
workshop-files/
index.html
slides.pdf
examples.zip
handouts/
day-1.pdf
day-2.pdf
Use clear file names that you are happy to read aloud. The names show in links, in browser downloads, and in search results.
2. Build the index page
Option A is to write the page by hand. For a few files, a short page with a description and labeled links gives the clearest result:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Workshop materials</title>
</head>
<body>
<h1>Workshop materials</h1>
<p>Slides and example files from the October workshop.</p>
<ul>
<li><a href="slides.pdf">Presentation slides (PDF)</a></li>
<li><a href="examples.zip">Example files (ZIP)</a></li>
<li><a href="handouts/day-1.pdf">Day 1 handout (PDF)</a></li>
</ul>
</body>
</html>
Use plain labels that give the content and the format of each file. Then people on a phone know what they will download.
Option B is to generate the page. For a folder that changes often, generate the list. This Python 3 script goes through the folder and its subfolders. It skips hidden files and existing index.html files. Then it writes an index.html with a link and the size for each file:
# make_index.py: write index.html that links every file in a folder
import html, os, sys
from urllib.parse import quote
root = sys.argv[1] if len(sys.argv) > 1 else "."
title = sys.argv[2] if len(sys.argv) > 2 else "Files"
items = []
for folder, dirs, files in os.walk(root):
dirs[:] = sorted(d for d in dirs if not d.startswith("."))
for name in sorted(files):
if name.startswith(".") or name == "index.html":
continue
full = os.path.join(folder, name)
path = os.path.relpath(full, root).replace(os.sep, "/")
size_kb = max(1, os.path.getsize(full) // 1024)
items.append(f'<li><a href="{quote(path)}">{html.escape(path)}</a> ({size_kb} KB)</li>')
page = f"""<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{html.escape(title)}</title>
</head>
<body>
<h1>{html.escape(title)}</h1>
<ul>
{chr(10).join(items)}
</ul>
</body>
</html>
"""
with open(os.path.join(root, "index.html"), "w", encoding="utf-8") as f:
f.write(page)
print(f"Wrote index.html with {len(items)} files")
Save the script outside the folder that you publish. Then run it with the folder and a page title:
python3 make_index.py workshop-files "Workshop materials"
The links are relative (they do not include the domain), so the page works at a root domain and in a subfolder. The script encodes file names with spaces or & correctly. If you add or remove files, run the script again. The script lists every file that it finds. This is one more reason to clean the folder first.
Option C is to let a server list the folder. If you run your own server, the server can generate a listing on every request. In Caddy, file_server browse shows a file list for any folder without an index file. The Caddy guide covers the setup. On a static host, use option A or B.
3. Deploy the folder
On Cloudflare Pages, you can deploy from the dashboard or from a terminal. In the Cloudflare dashboard, go to Workers & Pages. Select Create application and choose the drag-and-drop upload. Name the project and drop the folder. The site goes live at <project>.pages.dev. To deploy from a terminal, log in with npx wrangler login. Create the project with npx wrangler pages project create. Then upload the folder:
npx wrangler pages deploy workshop-files
On GitHub Pages, commit the contents of the folder to the root of a branch, or to a /docs folder. In the repository, go to Settings > Pages. Under Build and deployment, select Deploy from a branch. Choose the branch and the folder, and select Save (publishing source docs). On GitHub Free, the repository must be public. Also, Pages sites are public even when the repository is private.
Before you add the domain, open the new address and click every link.
4. Connect the custom domain
A DNS record is a public entry about a domain. You add the records at the DNS provider of the domain.
On Cloudflare Pages, follow these steps (custom domains docs):
- Open the project in Workers & Pages. Select Custom domains.
- Select Set up a domain. Enter the domain and select Continue.
- If the DNS of a subdomain such as
files.example.comis at a different provider, create aCNAMErecord there. ACNAMErecord points one name to another name. Point the record to<project>.pages.dev. If the domain is already on Cloudflare, Cloudflare adds the record for you. - An apex domain is a domain with no subdomain, such as
example.com. For an apex domain, the domain must be a zone on the same Cloudflare account. A zone is a domain that Cloudflare manages. The nameservers of the domain must point to Cloudflare.
Add the domain in Pages before you create the DNS record. Cloudflare warns that a CNAME does not resolve if you add it before you associate the domain in Pages. CAA records name the certificate authorities that can issue certificates for your domain. If your domain has CAA records, they must allow the certificate authorities that Cloudflare uses.
On GitHub Pages, follow these steps (custom domain docs):
- Verify the domain first. In your profile, go to Settings > Pages and select Add a domain. Create the TXT record (a DNS record that holds text) that GitHub shows (
_github-pages-challenge-<username>). Verification stops other people from taking over your subdomains. - In the repository, go to Settings > Pages. Enter the domain under Custom domain and select Save.
- For
wwwor another subdomain, create aCNAMErecord that points to<username>.github.io. Do not add the repository name. - For the apex domain, create
Arecords for185.199.108.153,185.199.109.153,185.199.110.153and185.199.111.153. AnArecord points a name to an IP address. - When the option appears, select Enforce HTTPS. The option can take up to 24 hours to appear.
Do not use wildcard DNS records such as *.example.com with GitHub Pages. GitHub says that they create a takeover risk, even for a verified domain.
To make sure that your records are correct, run dig with the record type. For example:
dig files.example.com +noall +answer -t CNAME
5. Update the files later
Add or remove files. Run the index script again. Redeploy to the same project, or push to the same repository. The domain stays connected, so you do not change anything in DNS. On Cloudflare Pages, deployed files stay cached until your next deployment. Then the new deployment serves the new version.
When the files must not be public
A custom domain does not make a folder private. If the files are for a client or a small group, you have two options. You can put the site behind Cloudflare Access. Cloudflare Access asks each visitor for a code that it sends to their email. Or you can share the documents through a tool that controls and records access, such as Papermark or DocSend.
A docs portal with Ask AI
and support tickets
Connect a repository, or describe your product to start. Each docs site has search, Ask AI, an API console, an MCP server and support tickets.
- Publishes from GitHub on every push
- Works with Mintlify repositories
- API console from your OpenAPI file
- MCP server and llms.txt for AI agents
- Ask AI and support tickets
- Analytics for people and AI agents
Related Articles

Add a Custom Domain to a Cloudflare Pages Site
Add a custom domain or www to a Cloudflare Pages site: set it up in Pages first, add the DNS record, fix stuck verification and redirect www.

What Is localhost:3000?
Your browser uses localhost:3000 to reach an app running on your own computer. Sending that address to someone else does not give them access to your app.

Move Exported Grok Website Files to Another Host
Export a Grok Build site to GitHub, deploy it on Cloudflare Pages, Netlify, Vercel or GitHub Pages, test it, then retire the grok.me copy.