Everything needed to take a folder of HTML files and put it on the internet, on your own domain, with HTTPS. Three parts — two for you, one for the agent.
What you will hand over. A ZIP file of your website, the API token from Part 1, and your domain name. That is all the agent needs.
This gives the agent permission to act on your Cloudflare account. It takes about two minutes. You need a Cloudflare account first — signing up at dash.cloudflare.com/sign-up is free.
Log in to dash.cloudflare.com. In the left sidebar, scroll to the bottom and open Manage account, then click Account API tokens.
Then click the blue Create Token button at the top right.
Use this page, not the profile one. There is a second, similar page under your user profile. Use Account API tokens as shown above. Tokens made here are scoped to the account, which is what the agent needs.
The create page shows only four templates by default. The one you need is not among them. Click View all templates.
From the expanded list, click Developer Services (19 permissions). This is the only template that covers deploying a site.
Clicking it creates two permission policies automatically:
The template does not include permission to manage DNS or to add domains. Without these, the agent cannot finish the job. Add them now.
Why these two are easy to miss. They only appear in the All Domains policy, not the account one — even though adding a domain feels like an account-level action. If you cannot find them, you are editing the wrong policy.
Give the token a name you will recognise later, such as site-deploy. Cloudflare pre-fills a
random name like super-boat-67f3; change it.
Under Token expiration, choose No expiration if this token will keep being used for future deploys. If it is for a single hand-over, pick 30 days and let it die on its own.
Click Review token and check the summary against the table below before clicking Create token.
| Scope | Must include |
|---|---|
| All zones | Workers Routes Write, DNS Write, Zone Write |
| Entire account | Pages Write, Workers Scripts Write, Workers R2 Storage
Read + Write, Workers KV Storage Read + Write, Account Settings Read, and
others from the template |
If DNS Write and Zone Write are missing, go back and redo step 1.4.
Copy the token immediately. Cloudflare shows the secret exactly once, on the screen right after you click Create token. If you close that dialog without copying it, the token is unrecoverable and you must make a new one.
Send it to the agent through whatever channel you already trust. Do not commit it to a Git repository.
It is in the address bar the whole time. The URL looks like:
https://dash.cloudflare.com/70a939330833ed084613ded7287a7d8d/api-tokens
That 32-character string is your account ID. It is not a secret — it appears in every dashboard URL — but the agent needs it. Send it along with the token.
This section is written for the AI agent doing the deployment. If you are the person who owns the website, you do not need to read, understand, or run any of it. Nothing here is a task for you. Skip to Part 3.
The agent receives three things: a ZIP of the site, the API token, and the domain name.
| Input | Example |
|---|---|
| Site archive | site.zip |
| API token | stored at ~/.ssh/CF_TOKEN — never echoed, never committed |
| Domain | example.com |
| Account ID | 70a939330833ed084613ded7287a7d8d |
tr -d to strip the trailing newline.export CLOUDFLARE_API_TOKEN=$(tr -d ' \t\r\n' < ~/.ssh/CF_TOKEN)
export CLOUDFLARE_ACCOUNT_ID=70a939330833ed084613ded7287a7d8d
export DOMAIN=example.com
export PROJECT=example-site
npx wrangler whoami
Expect the account name and ID.
Do not use /user/tokens/verify. It returns Invalid API Token
for account-scoped tokens because it is a User-scope endpoint. That is not a fault. Judge the token by
whether real calls succeed.
mkdir -p site && unzip -o site.zip -d site
find site -type f | head -50
Establish a public/ directory holding only what should be world-readable. Move the
site files there.
Never deploy the repository root. It will publish source archives, notes, tokens documentation and anything else lying around. Deploy an explicit directory containing only the site.
Check the HTML uses relative paths (href="styles.css"), not root-absolute
ones (href="/styles.css"). Absolute paths work on a server but break when the file is opened
directly from disk.
List zones first; the domain may already be there.
curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones?name=$DOMAIN"
If it is absent, create it. This needs Zone Write:
curl -s -X POST \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"name\":\"$DOMAIN\",\"account\":{\"id\":\"$CLOUDFLARE_ACCOUNT_ID\"},\"type\":\"full\"}" \
"https://api.cloudflare.com/client/v4/zones"
The response carries name_servers and original_name_servers. Record
both. The first pair is what the user must set at their registrar in Part 3; the second tells you
who currently runs their DNS.
This is the step that causes silent outages if skipped. Cloudflare's automatic import frequently finds nothing. If the nameservers change while records are missing, the website and — far worse — email stop working, and nobody notices for a day.
Check what Cloudflare actually imported:
curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?per_page=100"
Then query the real records straight from the current authoritative nameserver, taken from
original_name_servers:
for t in A AAAA MX TXT CNAME NS; do
echo "== $t =="
nslookup -type=$t $DOMAIN ns.theircurrentprovider.com
done
nslookup -type=A www.$DOMAIN ns.theircurrentprovider.com
dig is not installed on Windows and returns empty output rather than an
error — indistinguishable from a domain that does not resolve. Use nslookup.
Recreate anything missing, especially MX and TXT. Replicate faithfully — same
values, proxied: false — so the nameserver switch changes nothing:
curl -s -X POST \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"type":"MX","name":"example.com","content":"mail.host.com","priority":10,"ttl":1,"proxied":false}' \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records"
npx wrangler pages project create $PROJECT --production-branch=main
npx wrangler pages deploy public --project-name=$PROJECT --branch=main --commit-dirty=true
Expect Success! Uploaded N files and a live <project>.pages.dev URL. A
fatal: ambiguous argument 'HEAD' line is git complaining about a repository with no commits.
Harmless.
There is no wrangler pages domain command. Use the API. Needs Pages Write.
for d in $DOMAIN www.$DOMAIN; do
curl -s -X POST \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"name\":\"$d\"}" \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/pages/projects/$PROJECT/domains"
done
Each returns status: initializing.
Order matters. Attach the domain in the Pages project before creating the DNS record. Doing it the other way round produces a 522 error that looks like a server fault and sends you debugging the wrong thing.
Only once the site should go live. Needs DNS Write.
for n in $DOMAIN www.$DOMAIN; do
curl -s -X POST \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"CNAME\",\"name\":\"$n\",\"content\":\"$PROJECT.pages.dev\",\"ttl\":1,\"proxied\":true}" \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records"
done
The apex works as a CNAME only because Cloudflare flattens it. proxied: true is
required for that.
curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/pages/projects/$PROJECT/domains"
Poll until both read status=active. Roughly 90 seconds after DNS resolves. Nothing needs to
be done about certificates — Cloudflare issues and renews them itself.
# HTTP status
for u in https://$DOMAIN/ https://www.$DOMAIN/; do
curl -s -o /dev/null -w "%{http_code} %{content_type} $u\n" "$u"
done
# HTTP -> HTTPS redirect
curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}\n' http://$DOMAIN/
# certificate
echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
Finally, open the site in a browser and look at it. A 200 response does not prove the page renders.
Tell the user the live URLs, the certificate issuer and expiry, and — most importantly — the two Cloudflare nameservers from step 2.3, because Part 3 cannot happen without them.
What the agent cannot do. Changing nameservers happens at the registrar, which Cloudflare has no authority over. That step is always the user's. Hand them the two nameserver names and stop.
Everything else is finished by now. This is the switch that makes your domain start using Cloudflare, and it can only be done where you bought the domain.
You are not transferring your domain. It stays registered where you bought it. You keep paying and renewing there. Nothing about ownership changes.
You are only changing which servers answer questions about your domain. That is a free setting, and reversible.
If your domain receives email, read this. When nameservers change, any record that has
not been copied to Cloudflare stops working. Missing MX records means incoming mail
silently stops. It is the single most common way this migration goes wrong.
The agent should already have copied your records across in step 2.4. Ask them to confirm your
MX and TXT records are present before you change anything.
Also check whether your provider has DNSSEC enabled for the domain. If it does, turn it off before switching. DNSSEC signed by the old provider makes the domain completely unreachable once Cloudflare starts answering. You can switch it back on through Cloudflare later.
The agent will have given you two names that look like this:
coby.ns.cloudflare.com
noor.ns.cloudflare.com
They are specific to your account, so use the ones you were given, not these.
Log in where you bought the domain and find the nameserver setting. It is usually called Nameservers, DNS servers, Custom DNS or DNS management.
| Registrar | Where the setting lives |
|---|---|
| GoDaddy | My Products → Domain → DNS → Nameservers → Change → "I'll use my own nameservers" |
| Namecheap | Domain List → Manage → Nameservers → Custom DNS |
| Porkbun | Domain Management → Authoritative Nameservers → Edit |
| Squarespace (ex-Google Domains) | Domains → your domain → DNS → Nameservers → Use custom nameservers |
| Zone.ee | Domains → your domain → Nameservers |
| OVH | Domain → DNS servers tab → Modify DNS servers |
Replace, do not add. Delete the existing nameservers and put Cloudflare's two in their place. Leaving the old ones alongside causes intermittent failures where the site works for some visitors and not others — the hardest kind of problem to diagnose.
Cloudflare checks periodically and emails you when the domain becomes Active. This usually takes one to two hours. Registrars quote "up to 48 hours" as a worst case; that is rare.
You can check independently at any time:
nslookup -type=NS example.com
When that returns the two *.ns.cloudflare.com names, it has propagated.
https://example.com and
https://www.example.com. Both should load over HTTPS with no browser warning.MX record did not get copied — tell the agent and it can be fixed in a minute.Certificates need nothing from you. Not now, not at renewal, not ever. Cloudflare issues a certificate for each hostname automatically and renews it roughly every 90 days. There is no file to install, no key to store, no reminder to set.
| Symptom | Cause | Fix |
|---|---|---|
| 522 error on your domain | DNS record created before the domain was attached to the Pages project | Agent re-attaches it in the Pages project, then waits |
| Certificate stuck pending for hours | CAA records blocking Cloudflare's certificate authorities | Add CAA records allowing pki.goog, letsencrypt.org and ssl.com |
| Domain stuck Pending Nameserver Update | Nameservers not changed, or DNSSEC still on at the old provider | Recheck the registrar; disable DNSSEC |
| Email stopped arriving | MX records were not copied before the switch | Add them in Cloudflare's DNS page — mail resumes within minutes |
| Site works for some people, not others | Old and new nameservers both listed | Remove the old ones entirely |
Bare domain will not work, www does | DNS is not on Cloudflare | An apex domain requires Cloudflare nameservers. Complete Part 3. |