Error Budgets Explained: When to Stop Shipping Features

Getting Started with OpenObserve

Try OpenObserve Cloud today for more efficient and performant observability.

An error budget is the amount of unreliability your SLO allows, calculated as 1 minus the SLO target. It turns "be reliable" from a vague aspiration into a number you can spend: when the budget has room, ship freely; when it's gone, risky changes pause and the team works on reliability until it recovers. The part most teams get wrong isn't the math, it's what happens next. A budget with no written error budget policy behind it either gets ignored (nothing actually stops) or gets treated as an absolute kill switch (everything stops, including the incident fix that would have solved it). This post covers what an error budget is, what should actually freeze when it's burned, and how to write a policy with graduated tiers your team will actually follow, including a copy-paste template. OpenObserve covers the implementation side, SLI queries, budget dashboards, and burn-rate alerts, in How to Set Meaningful SLOs and SLO-Based Alerting.
Every engineering org runs the same conflict on a loop. Product and engineering want to ship: new features, faster releases, more experiments. SRE and ops want stability: fewer changes, more caution, longer review cycles. Without a shared number, that conflict doesn't get resolved, it gets refought every sprint, and whoever argues loudest (or whoever is most recently burned by an outage) wins for a few weeks until the pendulum swings back.
Two failure modes show up when there's no error budget:
Recklessness. Teams ship as fast as they want because nothing measures the cumulative risk they're taking on. Reliability degrades quietly, deploy after deploy, until a major outage forces a reckoning nobody saw coming, even though the warning signs were there in a dozen smaller incidents.
Over-caution. The opposite failure, usually triggered by the first failure. After a bad outage, every change becomes "risky" by default. Reviews multiply. Releases slow to a crawl. Six months later, the team has shipped almost nothing, users haven't noticed any reliability improvement they can point to, and the org quietly starts asking why velocity died.
Both failures share the same root cause: reliability and velocity are being managed as opinions, not as a measured tradeoff. There's also a subtler problem underneath both: chasing 100% uptime is not actually a rational goal. Users can't tell the difference between 99.9% and 99.99% reliability in daily use, but the engineering cost of closing that last 0.09% (redundant everything, exhaustive testing, slower releases) is enormous. Perfect reliability isn't free, it's traded directly against the speed at which you can improve the product.
An error budget fixes this by making the tradeoff explicit and numeric. Instead of "reliability" and "velocity" being two teams pulling in opposite directions, they become one shared number that both sides watch: how much unreliability is currently budgeted, and how much is left. When the number is healthy, product ships without needing anyone's permission. When it's gone, the org has already agreed, in writing, what happens next. The negotiation happens once, calmly, before there's a specific release on the line, not every time there's a specific release on the line.
An error budget is the allowable amount of unreliability derived from your SLO.
Error Budget = 1 - SLO target
If your SLO is 99.9% availability over a rolling 28-day window, your error budget is 0.1%, about 40 minutes of full downtime, or the equivalent spread across partial degradations and failed requests. That 0.1% isn't a defect to eliminate. It's a resource to spend on the things that cause failures in the first place: deployments, schema migrations, dependency upgrades, chaos experiments, infrastructure changes.
The reframing is the entire point. Without an error budget, "reliability" and "shipping fast" read as opposing goals, and the tension gets resolved by whoever argues louder in the incident channel. With one, both goals point at the same number:
If you haven't set an SLO yet, start with How to Set Meaningful SLOs, the error budget is only as meaningful as the SLI and target underneath it. An SLO built from infrastructure metrics like CPU or memory produces an error budget that doesn't track what users actually experience.
The formula never changes, only the target and the window do:
Error Budget = (1 - SLO target) × measurement window
Here's what that looks like at common SLO targets over a 30-day (43,200-minute) window:
| SLO Target | Error Budget (%) | Allowable Downtime / Month |
|---|---|---|
| 99% | 1% | ~7.3 hours |
| 99.5% | 0.5% | ~3.6 hours (216 min) |
| 99.9% | 0.1% | ~43.8 minutes |
| 99.95% | 0.05% | ~21.9 minutes |
| 99.99% | 0.01% | ~4.4 minutes |
Two things trip teams up here. First, this is the allowance for the whole window, not per incident, one bad deploy and three minor blips can all draw from the same budget. Second, consumption should be computed continuously from production telemetry (an SLI query run against your logs, metrics, or traces), not recalculated by hand after the fact. By the time a manual calculation catches an exhausted budget, it's usually been exhausted for days.
These three are related but answer different questions, and mixing them up is how teams end up tracking the wrong number. See SLA vs. SLO vs. SLI for the full breakdown; the short version:
| Term | What it is | Who sees it |
|---|---|---|
| SLA | The external, often contractual promise to customers | Customers, legal/business |
| SLO | The internal reliability target for an SLI | Engineering, internal |
| Error budget | 1 minus the SLO; the unreliability you're allowed to spend | Engineering, internal |
The SLO should always be stricter than the SLA, so your internal error budget runs out and triggers a response before you've breached the contractual promise, not after.
These get conflated constantly, and they answer different questions.
| Term | Question it answers | Example |
|---|---|---|
| Error budget | How much unreliability am I allowed, total, this window? | 0.1% over 28 days, about 40 minutes |
| Burn rate | How fast am I spending it, right now? | 14x means the 28-day budget is gone in ~2 days |
Error budget is the balance. Burn rate is the spending velocity. A team can have 80% of its budget remaining and still be in genuine trouble if the burn rate over the last hour is 40x, at that pace the remaining 80% disappears before lunch. This is why mature alerting is built on burn rate, not on waiting for the SLO to actually breach. The full mechanics, multi-window alerts, page vs. ticket thresholds, are covered in SLO-Based Alerting and Alert Fatigue: Why Teams Stop Trusting Alerts, so this post won't re-derive them. What matters here is the decision burn rate feeds into: when do you actually change what the team is doing.
Short answer: when the error budget for the affected service or user journey hits zero, or when the burn rate makes it obvious it will hit zero before the window resets. In a graduated policy, feature releases and other discretionary, risky changes pause at that point, while security patches, incident fixes, and rollbacks continue. The full tier breakdown is below, but that's the rule the rest of this section exists to operationalize.
This is where most error budget implementations quietly fail. The math gets built, the dashboard gets shipped, and then the first time the budget hits zero, nobody actually knows what's supposed to happen. Two failure modes show up repeatedly:
Nothing happens. The budget goes negative, the gauge turns red, and the team ships the same release schedule anyway because no one has the authority (or the appetite) to actually pause anything. The error budget becomes decoration.
Everything stops. The freeze is interpreted as absolute, so the incident-fix rollback and the emergency security patch get stuck behind the same approval gate as an unrelated feature launch. The first time this happens during a real incident, someone overrides it, and the policy loses credibility for the next twelve months.
Both failures come from the same root cause: the policy was never written down before it was needed. A durable error budget policy answers four questions in advance, while everyone is calm and nobody's release is on the line.
Not "everything." A workable policy distinguishes discretionary risk from necessary work:
Pauses when budget is exhausted:
Continues regardless of budget state:
If your policy doesn't draw this line explicitly, the freeze will get overridden the first time it's inconvenient, and every override after that is free.
Treating budget consumption as binary, healthy or exhausted, is why policies get ignored. A change that will actually hold up under pressure scales the response to how much budget is left:
| Budget Remaining | Status | Action |
|---|---|---|
| > 50% | Healthy | Ship normally |
| 10-50% | Watch | Extra review on high-risk changes; no policy change yet |
| < 10% | Critical | Only reliability, safety, and already-approved work ships |
| 0% (exhausted) | Frozen | Hard freeze on new features; named exceptions only |
The middle tiers matter more than the endpoints. A team that only defines "healthy" and "frozen" gets no warning before the freeze hits, and the freeze itself feels sudden and arbitrary. A graduated response gives the team time to adjust before a hard stop is necessary.
Name this in the policy, not in the moment. A common pattern:
If the approver isn't named in advance, the freeze gets negotiated service by service under deadline pressure, which is exactly the political dynamic error budgets exist to remove.
Budget ticking back above zero the instant an incident resolves is not the same as the system being trustworthy again. Most policies require the SLI to hold above target for a minimum stabilization period (commonly 24-72 hours) before the freeze lifts, not the moment the number crosses back over the line.
Here's the process, in the order you'll actually use it.
Step 1: Confirm the SLO and error budget are already agreed on. A policy for enforcing a budget nobody trusts will never survive contact with a real freeze. If the SLO is still contested, fix that first with How to Set Meaningful SLOs.
Step 2: Define the freeze tiers. Use the table above as a starting point, then adjust the thresholds to your team's actual risk tolerance and release cadence. A team shipping twice a week needs different tiers than a team shipping twice a quarter.
Step 3: List what's exempt, explicitly, by name. Security patches, incident fixes, rollbacks. Don't leave this to interpretation, someone will be arguing their change belongs in the exempt list at 2 AM, and a written list ends that argument before it starts.
Step 4: Name the approvers for each tier. Who signs off on a tactical exception. Who signs off on overriding the freeze entirely.
Step 5: Set the review cadence and the un-freeze condition. How long does the SLI need to hold above target before the freeze lifts. Who confirms it. When does the policy itself get revisited, quarterly is typical, so thresholds can be tightened as the team's reliability improves.
Step 6: Put it somewhere the whole team can find it before they need it. A policy in a doc nobody reads is not a policy, it's a wiki page. Link it directly from the SLO dashboard and from the on-call runbook for the affected service.
## Error Budget Policy: [Service / User Journey Name]
SLO: [e.g., 99.9% of checkout requests succeed within 500ms, 28-day rolling window]
Error Budget: [e.g., 0.1% = ~40 minutes/month]
Dashboard: [link to live error budget gauge]
### Response Tiers
| Budget Remaining | Status | Action |
|-------------------|----------|------------------------------------------------------|
| > 50% | Healthy | Ship normally |
| 10-50% | Watch | Extra review on high-risk changes |
| < 10% | Critical | Only reliability/safety work ships |
| 0% (exhausted) | Frozen | Hard freeze on new features; exceptions only |
### Always Allowed (regardless of budget state)
- Security patches
- Fixes for the incident that consumed the budget
- Rollback of the change that caused the burn
- Previously-approved reliability work
### Exception Approval
- Tactical exception (hotfix, rollback): [role/name]
- Full freeze override (new feature): [role/name] + [role/name]
### Un-Freeze Condition
- SLI must hold above target for [X hours/days] before freeze lifts
- Confirmed by: [role/name]
### Policy Review
- Reviewed: [cadence, e.g. quarterly]
- Owner: [role/name]
Adapt the tiers and thresholds to your context, the structure is what matters: written in advance, tiered rather than binary, with named approvers and an explicit exception list.
Writing the policy after the first freeze, not before. By the time a real freeze is happening, everyone involved has a stake in the outcome, which is the worst possible time to agree on ground rules.
No named exceptions. Every freeze without a pre-agreed exception list gets overridden the first time it blocks something urgent, and every override after that is free, so the second freeze gets ignored too.
Treating it as binary. All-or-nothing freezes either never trigger (too disruptive to actually enforce) or trigger too bluntly (block the fix that would resolve the outage). Graduated tiers are what make the policy survivable.
No stabilization period before un-freezing. Lifting the freeze the instant the SLI crosses back over target invites the same burn to happen again within days, because nothing about the underlying cause has actually been fixed yet.
Nobody reviews it. Thresholds set for a team shipping once a month don't fit the same team shipping daily six months later. Put a review cadence on the policy itself, not just the SLO.
An error budget is only actionable if consumption is computed from the same telemetry your incidents get debugged with. OpenObserve tracks SLI ratios from metrics and traces, and its alerting expresses burn-rate conditions directly against SQL queries, so the number driving your freeze policy and the number your team is staring at during an incident are the same number, not two dashboards that quietly drift apart.
Put the error budget gauge on an observability dashboard your whole team sees, not buried in a config file, and track how much budget each incident consumes as part of your MTTR reporting, so the cost of an outage is visible in both time and budget.
For the hands-on build, SLI queries, the budget gauge, and burn-rate alert rules, see SLO-Based Alerting in OpenObserve and How to Set Meaningful SLOs.
The error budget calculation is one line: 1 minus your SLO target. The hard part, and the part that actually changes team behavior, is what you've agreed happens next. A policy with graduated tiers, a named exception list, and named approvers turns "should we freeze the release?" from a political argument into a rule everyone already agreed to. Write it down before you need it, not during the incident that proves you needed it.
Start a Free Trial of OpenObserve to build your SLI queries and error budget dashboards, or explore self-hosting options.
Further reading: