How to Host a Static Website on Cloudflare
Deploy a static HTML site to Cloudflare Pages with Git, Direct Upload or C3, configure builds and domains, and fix common 404 and build errors.
How do I host a static website on Cloudflare? Put your deploy-ready files in a Cloudflare Pages project. The simplest workflow is Git integration: connect a GitHub or GitLab repository, choose the production branch, set the build command and output directory, then deploy. Cloudflare gives the project a pages.dev address. Plain HTML sites can use no build command; framework sites must publish the directory produced by their build.
Cloudflare documents three Pages deployment routes: Git integration, Direct Upload and C3 from the command line. Its Pages overview also recommends considering Workers for new projects because Workers supports most Pages use cases; this guide stays focused on Pages because it is the direct answer for hosting a static website.
1. Prepare the site files
Your output directory must contain the files that should be publicly served. For a plain site, the minimum is a top-level index.html.
my-site/
├── index.html
├── styles.css
├── app.js
└── assets/
└── logo.svg
Example index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My static site</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<main>
<h1>Hello from Cloudflare Pages</h1>
</main>
<script src="/app.js" defer></script>
</body>
</html>
Commit the files to GitHub or GitLab if you plan to use Git integration. Cloudflare Pages Git integration supports those hosted providers, not self-hosted Git instances. If your provider is different, use Direct Upload through CI and Wrangler.
2. Choose a Pages deployment method
| Method | Best for | What happens |
|---|---|---|
| Git integration | Sites updated by repository pushes | Cloudflare builds and deploys commits, with preview deployments for new pull requests |
| Direct Upload | Prebuilt files or custom CI systems | You upload the output directory yourself |
| C3 | Command-line setup | You create and deploy a Pages project from the terminal |
Choose carefully: Cloudflare documents that a Git-integrated project cannot later be converted to Direct Upload. See the Pages overview, Git integration guide and static HTML guide.
3. Deploy with Git integration
- Open Workers & Pages in the Cloudflare dashboard.
- Create an application, choose Pages, and import your GitHub or GitLab repository.
- Select the production branch. Cloudflare’s plain HTML example uses
main. - Set the project root directory if the site is inside a monorepo.
- Enter the build command and output directory described in the next section.
- Save and deploy. Pushes to the configured branch trigger later deployments.
After deployment, Cloudflare provides a pages.dev hostname. Open it and test the home page, assets and representative internal routes.
4. Configure the build command and output directory
The output directory is the directory Pages uploads after the build finishes. A successful command exits with code zero; a nonzero exit code fails the deployment.
| Site type | Build command | Output directory |
|---|---|---|
| Plain HTML/CSS/JavaScript | Leave blank or use exit 0 |
Directory containing index.html and deploy-ready assets |
| Vite | npm run build |
dist |
| Astro | npm run build |
dist |
| Hugo | hugo |
public |
| Next.js static export | npx next build |
out |
| Monorepo | Project-specific | Set the Pages root directory to the application folder |
Framework presets and versions can change. Confirm the active framework’s output settings when diagnosing a build; Cloudflare maintains the current list in its build configuration documentation.
5. Deploy prebuilt files with Direct Upload
Use Direct Upload when your CI system already produces the final directory or when your Git provider is not supported by Pages Git integration. Build the site in CI, then upload that output with Cloudflare’s documented Wrangler workflow. Keep the upload directory exact: it should contain the top-level index.html and all referenced assets.
For another Git provider, Cloudflare recommends starting with Direct Upload and deploying through a CI provider such as GitHub Actions using Wrangler. Follow the current Git integration guidance for authentication and command details.
6. Create a project with C3
C3 is Cloudflare’s command-line setup route. It is useful when you want project creation and deployment in a terminal-driven workflow. Run the current C3 command shown in Cloudflare’s Pages documentation, select Pages when prompted, and point the deployment at your generated output directory. C3 and Wrangler options evolve, so use the command and authentication steps in the live Pages documentation rather than copying an outdated command.
7. Fix a 404 on the pages.dev URL
If the root URL returns 404, check the output directory first. Cloudflare identifies a top-level index.html as the root document.
- Open the deployed output directory locally.
- Confirm
index.htmlis directly inside it, not inside another nested folder. - Confirm the Pages output-directory setting points to that directory.
- For a framework, run the build locally and inspect the generated directory.
- Redeploy and open the exact deployment URL.
For example, if your build creates dist/index.html, configure dist as the output directory, not the repository root.
8. Add a custom domain
In the Pages project, open Custom domains and follow the hostname setup flow.
- A subdomain can use the DNS configuration shown by the dashboard.
- An apex domain such as
example.commust be a zone in the same Cloudflare account, and its nameservers must point to Cloudflare.
Do not treat an apex domain as a CNAME-only setup. Use Cloudflare’s custom domains documentation for the active requirements.
9. Redirect pages.dev to the custom domain
If the custom domain should be the only public URL, first add and verify it, then create a Cloudflare Bulk Redirect from the project’s pages.dev hostname to the custom domain. Cloudflare documents this flow in Redirecting pages.dev to a custom domain.
10. Add redirects with _redirects
Place a plain-text file named _redirects in the asset directory so the build copies it into the final output. Each line describes a redirect.
/old-path /new-path 301
/docs /guide 302
Cloudflare documents a limit of 2,000 static redirects and 100 dynamic redirects, with 2,100 combined. These rules do not affect requests served by Pages Functions. Put applicable behavior in Function code or exclude those paths from Functions. See the redirect rules documentation.
11. Add response headers with _headers
A plain-text _headers file can add, override or remove headers for static asset responses. It is not served as an asset.
/*
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Choose security-header values for your application instead of copying examples blindly. _headers does not apply to Pages Functions responses; set headers in the Function response instead. Read the headers documentation before deploying policy changes.
12. Limits to check before launch
Cloudflare’s limits page was last updated September 5, 2026. Limits are plan-dependent and can change, so verify them before planning a large site.
| Documented limit | Free plan figure |
|---|---|
| Builds per month | 500 |
| Concurrent builds | 1 |
| Files per site | 20,000 |
| Maximum individual asset | 25 MiB |
| Custom domains per project | 100 |
| Build timeout | 20 minutes |
Paid plans list different limits, including up to 100,000 files per site when the documented PAGES_WRANGLER_MAJOR_VERSION=4 project setting is used. Check the live Pages limits page.
13. Performance, reliability and cost considerations
- Keep the output small: compress images, remove unused bundles and avoid shipping source files that are not needed at runtime.
- Cache deliberately: use hashed filenames for versioned assets so browsers and edge caches can retain them safely.
- Protect build capacity: avoid triggering unnecessary builds; the Free plan documents 500 builds per month and one concurrent build.
- Watch file and asset limits: split oversized assets or change the delivery design before reaching the 25 MiB per-asset and file-count limits.
- Validate every deploy: check the root page, one internal route, CSS, JavaScript, fonts and images from the generated hostname.
- Use previews: Git integration provides preview deployments for new pull requests, allowing route and asset checks before production.
Pages hosting costs and limits depend on the Cloudflare plan. Use Cloudflare’s current plan and limits pages for the values that apply to your account.
14. Troubleshooting checklist
| Symptom | Likely cause | Fix |
|---|---|---|
| Root URL returns 404 | index.html is nested or the output directory is wrong |
Put index.html at the output root and update the Pages output setting |
| Build fails immediately | Build command is missing, invalid or exits nonzero | Run it locally, verify dependencies and confirm it exits zero |
| Blank page after a successful build | Asset paths point to the wrong base URL | Inspect browser network errors and configure the framework’s base/public path |
| CSS or images return 404 | Files were not copied into the generated output | Check the output directory contents and use paths matching the deployed URL structure |
| Monorepo deploys the wrong app | Pages root directory points at the repository root | Set the project root to the application directory |
| Git provider cannot be connected | Provider is not GitHub or GitLab, or is self-hosted | Use Direct Upload through CI and Wrangler |
| Apex domain does not activate | Zone or nameservers are not configured in the same Cloudflare account | Add the domain as a Cloudflare zone and point nameservers to Cloudflare |
| Redirect file has no effect | It is outside the final asset directory or the path is handled by a Function | Copy _redirects into the output and move Function routes into Function code |
| Headers are missing | Response is generated by a Pages Function | Set headers in the Function response; _headers covers static assets |
| Build times out | Build exceeds the documented 20-minute timeout | Reduce build work, cache dependencies where supported, or split the site |
15. Verify the deployment
- Production branch and latest commit are correct.
- Build command exits successfully.
- Output directory contains top-level
index.html. - Home page, internal routes and static assets load from
pages.dev. - Pull-request preview behaves as expected.
- Custom domain resolves and HTTPS is active.
- Redirect and header files are in the final output.
- File counts, asset sizes and build frequency fit current plan limits.
Or skip the browser setup
If you need screenshots of the deployed Pages site for documentation, visual checks or previews, ScreenshotNeo captures a URL with one request. It accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before the shot. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server lets Claude, Cursor and other MCP clients call take_screenshot, get_page_info and capture_pdf.
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
See the ScreenshotNeo API documentation for the other capture options. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
FAQ
Can I host a site with no framework?
Yes. Use a blank build command or exit 0, and set the output directory to the folder containing your deploy-ready files and top-level index.html.
Can I change Git integration to Direct Upload later?
Cloudflare documents that a Git-integrated project cannot be converted to Direct Upload, so choose the deployment route before creating the project.
Does an apex domain work with only a CNAME?
No. An apex domain must be a zone in the same Cloudflare account with nameservers pointing to Cloudflare.
Where should _redirects and _headers live?
Put them in the asset directory that is copied into the final Pages output. Headers and redirects generated by Pages Functions require Function code instead.
Should a new project use Pages or Workers?
Cloudflare’s Pages overview says Workers supports most Pages use cases and advises considering Workers for new projects. Pages remains the focused workflow in this guide for static-site deployment.


