Dotted pixel art of an orange sunset glowing through dark clouds above a calm sea.

Clear Cloudflare's cache after a Coolify deploy

Author
gunlaug team
Updated
12 Nov 2026
Reading time
6 min

Photo by kien virak

If Cloudflare caches your pages, a Coolify deploy can finish while visitors still get the old ones. One post-deployment command empties the cache every time.

Why the old pages stay

Out of the box, Cloudflare doesn’t cache HTML. It decides by file extension, so CSS, scripts, images and fonts get cached and pages go straight to your server. Old pages after a deploy usually mean a Cache Rule, or an older Cache Everything Page Rule, tells it to cache them. This site has a Cache Rule: it matches every request, marks it Eligible for cache, and sets Edge TTL to “Use cache-control header if present, bypass cache if not”. That makes Cloudflare cache whatever the app allows it to.

On this site, a Next.js 16 app on Coolify, that rule gives these headers and cache results.

Cache headers from the app and from Cloudflare for each kind of response on gunlaug.no
ResponseFrom the appFrom CloudflareRepeat request
Homepages-maxage=31536000max-age=14400, s-maxage=31536000HIT
Article pages-maxage=31536000max-age=14400, s-maxage=31536000HIT
Article data (?_rsc)s-maxage=31536000max-age=14400, s-maxage=31536000HIT
Hashed scriptpublic, max-age=31536000, immutablepublic, max-age=31536000, immutableHIT
Imagepublic, max-age=31536000, immutablepublic, max-age=31536000, immutableHIT
robots.txtpublic, max-age=0, must-revalidatepublic, max-age=14400, must-revalidateEXPIRED
sitemap.xmlpublic, max-age=0, must-revalidatepublic, max-age=14400, must-revalidateEXPIRED

Next.js marks prerendered pages, and the data it fetches for client-side navigation, with s-maxage=31536000: shared caches may keep them for a year. With the rule above, Cloudflare does exactly that. When we checked, the copy of the homepage at the edge was 2.9 days old. Scripts under /_next/static/ are cached for a year as well, but their names change whenever their content does, so an old copy never stands in for a new file. Pages keep their URLs, and those are the ones that go stale.

Cloudflare also changed what browsers get. The app sent no max-age on its pages and max-age=0 on robots.txt, and Cloudflare turned both into max-age=14400. That comes from Browser Cache TTL, which defaults to four hours and replaces any lower value. A purge empties Cloudflare’s cache only, so a browser can still show an old page for up to four hours. Setting Browser Cache TTL to Respect Existing Headers, under Caching and then Configuration, stops Cloudflare from adding it.

When you don’t need this

Request one of your pages twice and read cf-cache-status. If it says DYNAMIC, Cloudflare isn’t caching pages and a deploy shows up at once. The files it does cache by default only go stale if you replace one under the same name, such as an image you overwrite. Rename the file instead and nothing needs purging.

You can also drop the Cache Rule. Next.js serves prerendered pages from its own cache, which the x-nextjs-cache: HIT header on this site shows, so a small site on one server loses little. Caching pages at Cloudflare helps most when your visitors are far from the server.

Create a token that can only purge

  1. In the Cloudflare dashboard, open My Profile, then API Tokens, and create a custom token.
  2. Give it one permission: Zone, Cache Purge, Purge.
  3. Under Zone Resources, include only the specific zone for your domain.
  4. Copy the Zone ID from the API section of the domain’s Overview page.

If the token leaks, the worst anyone can do with it is empty your cache.

The script

// scripts/purge-cloudflare.mjs
const zone = process.env.CLOUDFLARE_ZONE_ID;
const token = process.env.CLOUDFLARE_PURGE_TOKEN;

async function purge(label) {
  const res = await fetch(
    `https://api.cloudflare.com/client/v4/zones/${zone}/purge_cache`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ purge_everything: true }),
    },
  );
  const data = await res.json().catch(() => ({}));
  console.log(res.ok && data.success
    ? `Cloudflare cache purged (${label}).`
    : `Cloudflare purge failed (${label}): ${res.status}`);
}

if (!zone || !token) {
  console.log("Purge skipped: zone ID or token not set.");
} else {
  await purge("now");
  await new Promise((resolve) => setTimeout(resolve, 30_000));
  await purge("after 30 s");
}

It needs Node 18 or later for the built-in fetch. When either variable is missing it prints a line and exits, and a failed purge is logged and never thrown, so the script can’t break a deploy. It purges twice, 30 seconds apart, because of how Coolify swaps containers.

Run it from Coolify

Open the application in Coolify. On the General page, under Deployment, set the post-deployment command to node scripts/purge-cloudflare.mjs. Coolify runs it through sh -c inside the newly deployed container, after it has marked the deployment complete. The script therefore has to be inside your image. A Dockerfile that copies the whole repository includes it. If yours copies only Next.js’s standalone output, copy the script in as well.

Add CLOUDFLARE_ZONE_ID and CLOUDFLARE_PURGE_TOKEN under Environment Variables, and make the token runtime only. Coolify warns that values available during the build can end up in the image’s metadata. A change to a runtime-only variable needs a restart before the container sees it.

The second purge is for the rolling update. Coolify starts the new container, waits until it is healthy, and only then stops the old one, which gets a stop grace period of 30 seconds by default. Its documentation doesn’t say whether the old container is gone by the time the post-deployment command runs. If it still answers a request after the first purge, Cloudflare stores an old page again, and the second purge clears it.

A post-deployment command that fails is written to the deployment log, and the deployment still shows as successful. After your next deploy, open its log and look for both “Cloudflare cache purged” lines.

Dotted pixel art of lavender rows in blue and violet under a pale evening sky.Photo by Volker Meyer

Check that it worked

curl -sI https://example.com/ | grep -iE '^(cf-cache-status|age):'

Run this right after a deploy. The first request should say MISS, and the next one HIT with a small age. An age that goes back to before the deploy means the purge didn’t run. Cloudflare’s API reports success once it has accepted a purge, so check the header on the next request instead. If the site doesn’t load at all after a deploy, the cause lies elsewhere, and our guides to error 522 and error 526 cover the same Coolify setup.

Everything, or only some URLs

Every plan can purge by URL, hostname, tag, prefix or everything. On the Free plan, every type except single URLs is limited to 5 requests a minute per account, with a burst of 25. Single URLs allow 800 a second. Each request takes up to 100 URLs, prefixes or hostnames.

On a Next.js site, purging single URLs leaves part of each page behind. Cloudflare’s cache key includes the query string, and the data for client-side navigation is fetched from the page URL with an _rsc query string added. In the table above, that data was cached separately from the page. A purge of the page URL leaves it in place. A prefix purge removes every query string under the prefix:

{ "prefixes": ["example.com/blog/"] }

Purging everything is the simplest choice for a small site. Cloudflare warns that every request then goes to your server until the cache fills again, and recommends targeted purges for busy sites. This site purges everything, because its pages are prerendered and cheap for the server to send again.

How this was tested

On 12 November 2026 we requested seven URLs on gunlaug.no, a Next.js 16 site on Coolify behind Cloudflare’s Free plan. Each URL was fetched twice through Cloudflare and once straight from the server with curl --resolve, and the zone’s Cache Rule and cache settings were read through the API. The headers are in the raw JSON. We did not run a purge as part of this test, so what a purge does and its limits come from Cloudflare’s documentation. The script above is a shortened copy of the one in this site’s repository. This article was prepared with help from AI.

Common questions

Does a purge clear my visitors’ browsers?

No. It empties Cloudflare’s copies only. With the default Browser Cache TTL, a browser can keep a page for four hours. Set Browser Cache TTL to Respect Existing Headers if pages should update for returning visitors too.

Visitors with the site open get errors after a deploy. Why?

Their open page belongs to the old build and may ask for script files the new container no longer has. Setting deploymentId in next.config makes Next.js notice the mismatch and reload the page.

Can I purge without a script?

We found no built-in Cloudflare purge in Coolify’s documentation. You can click Purge Everything under Caching and then Configuration in the dashboard after each deploy, or put the same API call in your CI after it triggers the deploy. The post-deployment command needs no one to remember it.

Sources

Cloudflare, Coolify and Next.js documentation, checked on 12 November 2026, and the test record linked above.