Skip to content
FLAVIO COPES
flaviocopes.com
2026

How to rebuild a Cloudflare site on a schedule

By Flavio Copes

Learn how to rebuild a Cloudflare Pages site on a schedule using a deploy hook, triggered either by a GitHub Action cron or a Cloudflare Worker cron.

~~~

A static site on Cloudflare Pages rebuilds when you push to Git. Most of the time that’s exactly what you want.

But sometimes you want it to rebuild on its own, on a schedule, with no push at all.

I hit this with scheduled blog posts. I write a post, give it a future date, and the site hides it until that day. But “until that day” only works if the site rebuilds on that day. Nobody’s pushing at 7am to make a post go live.

The same need comes up for a site that shows data which goes stale: prices, a changelog, anything pulled in at build time.

Here’s how I solved it, and the trade-off I had to pick between.

The key piece: a deploy hook

Cloudflare Pages has a feature called a deploy hook. It’s a secret URL. POST to it, and Cloudflare runs a fresh build and deploy, exactly as if you’d pushed.

You create one in the dashboard: open your Pages project, then Settings → Builds & deployments → Deploy hooks. Give it a name, pick the branch to build (for me, master), and you get a URL.

Test it from your terminal:

curl -X POST "https://api.cloudflare.com/client/v4/pages/webhooks/deploy_hooks/your-hook-id"

Run that, check your dashboard, and you’ll see a new build start. That’s the whole trick.

Now you just need something to press that button once a day. There are two good ways, and this is the real decision.

Option 1: a GitHub Action

If your code already lives on GitHub, the simplest trigger is a scheduled workflow that curls the hook.

Create .github/workflows/scheduled-rebuild.yml:

name: Daily rebuild

on:
  schedule:
    - cron: "15 5 * * *"
  workflow_dispatch: {}

jobs:
  rebuild:
    runs-on: ubuntu-latest
    steps:
      - run: curl -fsS -X POST "${{ secrets.CF_PAGES_DEPLOY_HOOK }}"

Put the hook URL in a GitHub secret called CF_PAGES_DEPLOY_HOOK (repo Settings → Secrets and variables → Actions), so it’s not in your code.

That workflow_dispatch line lets you also trigger it by hand from the GitHub UI, which is handy for testing.

The good:

The not-so-good:

Option 2: a Cloudflare Worker cron

The other way keeps everything on Cloudflare. You make a tiny Worker that runs on a schedule and fetches the hook.

The Worker is almost nothing:

export default {
  async scheduled(event, env, ctx) {
    await fetch(env.DEPLOY_HOOK, { method: 'POST' })
  },
}

Its wrangler.jsonc sets the schedule:

{
  "name": "daily-rebuild",
  "main": "src/index.ts",
  "compatibility_date": "2026-06-20",
  "triggers": {
    "crons": ["15 5 * * *"]
  }
}

Store the hook URL as a secret and deploy:

npx wrangler secret put DEPLOY_HOOK
npx wrangler deploy

The good:

The not-so-good:

Which one I’d pick

Here’s the thing people get wrong: a Worker cron is not “simpler.” Both options hit the same deploy hook. The Worker can’t build your site itself, since Cloudflare’s build system is what runs your build. So all the Worker does is POST the hook, same as the Action.

The difference is just the wrapper around that POST. The GitHub Action is one file in a repo you already have. The Worker is a whole separate deployable.

So my rule of thumb:

Both work. Pick based on where you want the moving part to live.

One gotcha: time zones

Whichever you choose, the cron runs in UTC. My posts go live at 07:00 my time, which is 05:00 UTC, so I schedule the rebuild at 15 5 * * *, a little after, to be safe.

If you set a schedule and the rebuild seems to happen at the wrong hour, this is almost always why.

That’s the whole pattern

A deploy hook turns “rebuild my site” into a single POST. A scheduler presses it on a timer. With that, a static site on Cloudflare can publish scheduled content, refresh stale data, or redeploy nightly, all without you touching anything.

The deploy hook details are in the Pages docs, and the Worker schedule side is in the Cron Triggers docs.

~~~

Related posts about cloudflare: