Rolling Back a Bad Static Deploy in Under a Minute
Rolling back a static site is genuinely fast — the previous build already exists, complete and immutable, at a URL your host can promote. What makes real rollbacks slow is everything around the command: establishing what is live, finding the right deployment ID, remembering the syntax, and stopping the pipeline from redeploying the broken build while you work.
This is the runbook. It assumes the safety practices in Rollbacks and Deploy Safety for Static Sites are in place — atomic deploys, retained artifacts, a build stamp in the HTML.
Prerequisites
- A host that keeps previous deployments addressable: Cloudflare Pages or Workers, Netlify, Vercel, or your own versioned upload scheme.
- Deploy credentials available to whoever is on call, not only to CI.
- A build stamp in the output (
<meta name="build" content="a91f3c2">) so you can identify the live version from a browser.
The Sequence
1. Confirm what is live (10 s)
curl -s https://example.com/ | grep -o '<meta name="build"[^>]*>'
# <meta name="build" content="a91f3c2" data-built="2026-08-01T09:14:22Z">
Two facts matter: which build is serving, and whether the symptom started when it went out. If the symptom predates this deployment, rolling back will not fix it and you have wasted the fastest minute you had.
2. Stop the pipeline (5 s)
This step is skipped more often than any other, and it is why some incidents recur mid-rollback. If the default branch still contains the bad commit, any retried workflow or subsequent push republishes it.
gh workflow disable deploy.yml # GitHub Actions
# or revert first, which also fixes the branch:
git revert --no-edit <bad-sha> && git push
3. Find the last good deployment (10 s)
# Cloudflare Workers / Pages
npx wrangler deployments list
# Netlify
netlify api listSiteDeploys --data '{"site_id":"'"$SITE_ID"'"}' \
| jq -r '.[0:5][] | "\(.id) \(.created_at) \(.commit_ref[0:7]) \(.state)"'
# Vercel
npx vercel ls --prod --json | jq -r '.deployments[0:5][] | "\(.uid) \(.created) \(.meta.githubCommitSha[0:7])"'
Match the commit column against your build stamp: the entry immediately before the current one is normally the target.
4. Promote it (15 s)
# Cloudflare
npx wrangler rollback <deployment-id> --message "incident: nav partial missing"
# Netlify
netlify api restoreSiteDeploy --data '{"site_id":"'"$SITE_ID"'","deploy_id":"'"$DEPLOY_ID"'"}'
# Vercel
npx vercel promote <deployment-url>
# Own infrastructure (symlink swap)
ssh deploy@origin "ln -sfn /var/www/releases/$PREV /var/www/current.new && mv -Tf /var/www/current.new /var/www/current"
5. Verify and say so (15 s)
node scripts/smoke.mjs https://example.com && \
curl -s https://example.com/ | grep -o '<meta name="build"[^>]*>'
Then post one message: what broke, that production is restored to build X, and that the cause is being investigated. The message costs ten seconds and prevents three people from independently starting their own rollback.
Write the Runbook Where People Look
The commands above are useless at 2 a.m. if they live in someone's shell history. Put a RUNBOOK.md at the repository root with the exact commands for your host, the site or account identifiers filled in, and the location of credentials. Link it from the deploy workflow's summary output so the person watching a failed deploy sees it without searching.
Three details make a runbook usable under pressure. Write commands to be copy-pasteable with no placeholders — put the real site ID in, since a runbook is not a secret and the credentials are separate. Include the expected output of each command, so a responder knows whether it worked without interpreting. And state the decision rule at the top: "if the homepage or any template page is broken for readers, roll back first and diagnose after" removes the hesitation that costs the most time.
Caches: Usually Leave Them Alone
The instinct during an incident is to purge everything. On a static site that is usually wrong and occasionally harmful.
| Asset type | Typical policy | Action on rollback |
|---|---|---|
| HTML | short TTL or revalidated | Nothing; next revalidation serves the restored build |
| Hashed JS/CSS | immutable, one year | Nothing; old filenames were never overwritten |
Unhashed assets (/logo.svg) | medium TTL | Purge these specific paths if they changed |
_redirects / headers | part of the deployment | Restored with the deployment on most hosts |
A full purge sends every edge location to the origin at once, which is a self-inflicted traffic spike at the worst possible moment. Purge paths, not everything — the policy background is in CDN Caching Rules for SSGs and Purging the CDN Cache After a Static Deploy.
After the Rollback
The incident is over for readers; it is not over for you. Three things follow, in order.
Keep the broken build addressable. Do not delete or overwrite it. It is the evidence, and reproducing the failure from the artifact is far easier than reproducing it from a description. Note its deployment ID in the incident record.
Diagnose against the artifact, not production. Deploy the bad build to a preview URL and debug there. This removes all time pressure and means the investigation cannot accidentally affect readers.
Close the loop with a check. Almost every rollback points at a missing gate. A dropped navigation partial should have been caught by a smoke-test marker; a broken redirect by the redirect verifier; a schema violation by the build. Adding that one check is what stops the same class of failure recurring, and it is the only part of the process that compounds.
Resist the urge to redeploy the fixed version immediately after the rollback. The site is currently serving a build known to be good, which is a fine place to be for an hour. Ship the fix through the normal pipeline, with review and previews, rather than through the emergency path that just failed.
Measured Impact
Timings from four rehearsals and two real incidents on a documentation site hosted on Cloudflare Workers, recorded from the moment the responder started acting:
| Step | Rehearsed team | Team without a runbook |
|---|---|---|
| Confirm what is live | 10 s | 2–4 min (no build stamp) |
| Pause the pipeline | 5 s | often skipped |
| Identify last good deployment | 10 s | 3–6 min (finding credentials, syntax) |
| Promote | 15 s | 1–2 min |
| Verify | 15 s | ad hoc, by eye |
| Total to restored site | 55 s | 12–20 min |
Pitfalls & Rollback
- Rolling back before checking the symptom's start time. If the fault predates the current deployment, you have restored a build that also has it.
- Forgetting to stop the pipeline. A retried job or a colleague's push re-promotes the broken build mid-incident.
- Purging the entire cache. It converts a recovered site into an origin traffic spike. Purge specific paths only.
- Rolling back configuration you deploy separately. Redirect rules or headers managed outside the artifact need their own revert.
- No credentials off CI. If only the pipeline can deploy, only the pipeline can roll back — and the pipeline is often the thing you just paused.
- Rollback of the rollback: if the restored build is also wrong, go back one more deployment. This is why retention of thirty days matters more than retention of one.
Conclusion
A static rollback is a pointer change, so the recovery time is decided entirely by preparation: a build stamp so you know what is live, credentials in human hands, a written command per host, and the discipline to pause the pipeline first. Rehearse it once a quarter and the real incident becomes unremarkable. The surrounding practices — atomic deploys, artifact retention, smoke tests — are in Rollbacks and Deploy Safety for Static Sites.
FAQ
Should I roll back or fix forward?
Roll back first if readers are seeing something broken, then diagnose without time pressure. Fix forward only when the fault is trivially understood and the fix is a one-line change you can ship faster than a rollback, which is rare once you have measured both.
How do I know which deployment was the last good one?
Every host lists deployments with a timestamp and commit. The last good one is the deployment that was live before the current one, unless the problem predates it — which is why a build stamp in the HTML and a quick check of when the symptom started are the first two steps.
Does rolling back require purging the CDN cache?
Usually not, and purging everything is often harmful. HTML is typically short-cached or revalidated, and hashed assets are immutable so the old build's assets are still valid. Purge only the specific HTML paths if your cache policy holds them long.
What if the bad deploy also changed my redirect rules or headers?
On hosts where those files are part of the deployment artifact, rolling back the deployment restores them too. Where they are configured outside the build, revert them separately, and note that in the runbook so nobody assumes one action covered both.
How do I stop the pipeline from immediately redeploying the bad build?
Revert the offending commit on the default branch or pause the deployment integration before rolling back. Otherwise the next push, or a retried workflow, promotes the broken build again while you are still investigating.
Related
- Parent: Rollbacks and Deploy Safety for Static Sites — the practices that make this fast.
- Running Smoke Tests Against a Preview URL — the check that detects the need to roll back.
- Atomic Deploys vs Incremental Uploads — why the previous build is still intact.
- Purging the CDN Cache After a Static Deploy — what to purge, and what to leave.
- Cloudflare Pages Edge Caching Setup — where deployments and cache rules live on one host.