Rolling Back Netlify Deploys From the CLI and API
Netlify keeps every deploy, and the dashboard has a "Publish deploy" button on each one. During an incident, that button is not always within reach: the person on call may not have dashboard access, the team may require changes to go through auditable automation, or the rollback may need to be part of a script that also notifies people and checks the result. Netlify's API exposes the same operation, and wrapping it gives a rollback that anyone with repository access can trigger in one click, that locks out the next automatic deploy, and that verifies the site afterwards.
This guide builds that for a marketing site and docs on Netlify: finding the right deploy, restoring it through the API, locking auto-publishing, verifying, and unlocking when the fix lands. It belongs to Rollbacks and Deploy Safety for Static Sites; the general case is in Rolling Back a Bad Static Deploy in Under a Minute.
Prerequisites
- A Netlify personal access token or, better, a token for a dedicated team member account used only by automation, stored as
NETLIFY_AUTH_TOKEN. - The site ID, stored as
NETLIFY_SITE_ID. - The Netlify CLI (
netlify-cli) orcurlandjq.
Step 1: List Recent Production Deploys
curl -s -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
"https://api.netlify.com/api/v1/sites/$NETLIFY_SITE_ID/deploys?production=true&state=ready&per_page=10" |
jq -r '.[] | [.id, .created_at, .commit_ref[0:7], (.title // "")[0:60]] | @tsv'
66f3a1… 2026-07-14T10:12:03Z 9c4e2b1 Update pricing table component
66f39b… 2026-07-14T08:40:51Z 41d7a0e Fix typo in getting started
66f2e0… 2026-07-13T16:22:10Z b88f312 Add September release notes
production=true excludes deploy previews and branch deploys; state=ready excludes failed builds. The deploy's commit and title make the right target obvious.
Step 2: Restore and Lock
Restoring publishes an existing deploy again. Locking stops the next push to main from automatically replacing it:
DEPLOY=66f39b… # the last good deploy
curl -s -X POST -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
"https://api.netlify.com/api/v1/sites/$NETLIFY_SITE_ID/deploys/$DEPLOY/restore" | jq -r '.state'
curl -s -X POST -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
"https://api.netlify.com/api/v1/deploys/$DEPLOY/lock" | jq -r '.locked'
The CLI equivalent uses netlify api restoreSiteDeploy --data '{"site_id": "…", "deploy_id": "…"}' and netlify api lockDeploy. Restoring took 3–5 seconds to go live in drills; nothing is rebuilt, and the deploy's own _redirects, _headers and functions come with it.
Step 3: Wrap It in a Workflow
name: rollback
on:
workflow_dispatch:
inputs:
deploy_id: { description: 'Netlify deploy ID to restore (blank = previous production deploy)', required: false }
jobs:
rollback:
runs-on: ubuntu-latest
environment: production-rollback # requires approval from a maintainer
steps:
- uses: actions/checkout@v4
- name: Restore and lock
env: { NETLIFY_AUTH_TOKEN: '${{ secrets.NETLIFY_AUTH_TOKEN }}', NETLIFY_SITE_ID: '${{ secrets.NETLIFY_SITE_ID }}' }
run: node scripts/netlify-rollback.mjs "${{ inputs.deploy_id }}"
- name: Smoke checks
run: npx playwright test -c smoke/playwright.config.ts
- if: always()
run: node scripts/notify.mjs "Rollback ${{ job.status }}: ${{ inputs.deploy_id || 'previous' }}"
The script picks the second most recent ready production deploy when no ID is given, restores and locks it, and prints both deploy titles to the job log. The production-rollback environment requires one maintainer approval, which in practice is given by whoever is on the incident call within a minute — and it means the rollback is recorded with who approved it.
Step 4: Verify the Restore
The smoke checks request a dozen key pages and their assets, confirm a 200 for each, and compare the build ID in a <meta name="build-id"> tag with the restored deploy's commit. A mismatch means the restore did not take — for example, if someone published another deploy from the dashboard at the same moment. Post-Deploy Verification Checklists lists what else to check.
Step 5: Unlock When the Fix Lands
While locked, pushes to main still build, and their deploys are ready but not published. When the fix is merged and its deploy is verified as a preview, an unlock workflow unlocks the locked deploy and publishes the fix:
curl -s -X POST -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" "https://api.netlify.com/api/v1/deploys/$LOCKED/unlock"
curl -s -X POST -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
"https://api.netlify.com/api/v1/sites/$NETLIFY_SITE_ID/deploys/$FIX_DEPLOY/restore"
A reminder posts to the incident channel every two hours while a deploy is locked, because a forgotten lock silently stops every subsequent release.
Rollback or Fix Forward?
Rollback is not always the right response, and the runbook says when to use it. A rollback restores the whole site to an older deploy, including every other change merged since. If three content pull requests merged after the bad one, rolling back removes them too until the fix lands. The team's rule of thumb:
In the four incidents since the workflow existed, two were rollbacks (a broken navigation component and a redirect rule that sent every docs page to the home page), one was a forward fix (a typo in a heading anchor), and one was neither — a third-party consent script outage, which no deploy could fix and which the team handled by temporarily removing the script through its tag configuration. Writing the decision down kept on-call people from reaching for the rollback button reflexively, and from hesitating when it was clearly the right call.
Deploy Previews as Rollback Candidates
Because every production deploy stays available, the workflow can also publish a deploy that was never live — for example, the preview of a pull request that reverts the bad change, once its checks pass. Restoring that deploy publishes the revert without waiting for a merge and a new build, which saved about three minutes in one incident. The lock is applied the same way, and the revert is merged normally afterwards so main matches what is live.
Drilling It
A rollback path that is never used breaks quietly — a token expires, an API endpoint changes, an approver leaves. The team runs the workflow every quarter against a staging site that mirrors production's configuration, timing each step, and treats any failure as an incident in its own right. The first drill found the token had been scoped to the wrong team; the second found the smoke checks pointed at an old hostname.
Measured Impact
Quarterly rollback drills, before and after the workflow:
| Measure | Dashboard rollback | Workflow rollback | Source |
|---|---|---|---|
| Median time from alert to old deploy live | 4 min 20 s | 1 min 22 s | Drill logs |
| People able to roll back | 3 (dashboard owners) | 14 (repo maintainers, with approval) | Access lists |
| Rollbacks undone by a later merge | 1 of 4 incidents | 0 | Incident log |
| Rollback verified automatically | No | Yes | Workflow |
The "undone by a later merge" row was the most important: in one earlier incident, a teammate merged an unrelated change twenty minutes after a dashboard rollback, and the broken deploy's change came back with it.
Pitfalls & Rollback
- Restoring without locking. The next merge republishes the broken change.
- Forgetting to unlock. Remind until the lock is removed.
- Personal tokens of individuals in CI. Use an automation account's token.
- Assuming data rolls back. Functions roll back; external data does not.
- Rollback of the rollback: restore the newer deploy again by ID — every deploy remains available.
Conclusion
Netlify's API makes rollback scriptable: list production deploys, restore the last good one, lock it so the next merge does not undo it, verify, and unlock when the fix is ready. Wrapped in a GitHub Actions workflow with an approval gate, it cut median rollback time in drills from over four minutes to under a minute and a half, opened rollback to every maintainer, and ended rollbacks being silently reversed.
FAQ
How do I roll back a Netlify deploy without the dashboard?
Call the Netlify API's restore endpoint for the deploy you want, which publishes that existing deploy again without rebuilding. The Netlify CLI's api command calls the same endpoint. Wrapping it in a GitHub Actions workflow with a manual trigger makes it a one-click operation for anyone with access to the repository.
Does restoring a deploy rebuild the site?
No. Netlify keeps every deploy's files, and restoring publishes an existing deploy as it was, including its redirects and headers files. It takes effect in seconds. Environment variables and build settings are not involved, because nothing is built.
Why does Netlify publish the next build over my rollback?
Because auto-publishing is still on. The next push to the production branch builds and publishes as usual, replacing the restored deploy. Lock the published deploy, which stops auto-publishing, until the fix is merged, then unlock it.
Are functions rolled back too?
Yes. Netlify Functions and Edge Functions are part of each deploy, so restoring an older deploy also restores the function code it shipped with. Data the functions wrote to external services is not rolled back, so check whether a bad deploy changed stored data separately.
Related
- Parent: Rollbacks and Deploy Safety for Static Sites — safe deploys and fast recovery.
- Rolling Back a Bad Static Deploy in Under a Minute — rollback across hosts.
- Atomic Deploys vs Incremental Uploads — why Netlify can restore instantly.
- Netlify Build Hooks for Content Updates — other ways deploys get published.
- Deploying to Cloudflare From GitHub Actions With Wrangler — the equivalent rollback on Cloudflare.