# Prometheus burn-rate alerts: PromQL and recording rule examples

Source: https://steadynines.com/learn/prometheus-burn-rate-alerts
Author: Balavigneshwaran M, SteadyNines
Last updated: 2026-11-10

In Prometheus, a burn-rate SLO alert divides the rate of bad requests by the rate of all requests over two windows and fires when both are above the SLO’s allowed error ratio times the burn-rate threshold. For a 99.9% SLO, the fast-burn rule fires when the 1-hour and 5-minute error ratios are both above 1.44%.

**Short answer:** For a **99.9% SLO**, the allowed error ratio is 0.001. A Prometheus burn-rate alert fires when the error ratio is above **threshold × 0.001** in both a long and a short window: **14.4× (1.44%) over 1 hour and 5 minutes** for a fast burn, **6× (0.6%) over 6 hours and 30 minutes** for a sustained burn, and **1× (0.1%) over 3 days and 6 hours** for a slow burn.

## What you need before you start

The examples below assume:

- `http_requests_total` counts eligible HTTP requests.
- The `outcome` label identifies requests as `good` or `bad`, using the same definition as the [SLI](https://steadynines.com/learn/what-is-an-sli).
- Each series has a `service` label.
- The SLO is 99.9% over 30 days, so the allowed error ratio is 0.001.

The thresholds come from the standard multi-window table. [Multi-window burn-rate alerting](https://steadynines.com/learn/multi-window-burn-rate-alerts) explains how they are calculated.

| Burn | Burn rate | Error ratio above | Long window | Short window | Response |
|---|---|---|---|---|---|
| Fast | 14.4× | 0.0144 | 1h | 5m | Alert on-call now |
| Sustained | 6× | 0.006 | 6h | 30m | Alert on-call |
| Slow | 1× | 0.001 | 3d | 6h | Ticket |

## Single PromQL example: the fast-burn condition

Written out in full, without recording rules, the fast-burn condition is:

```promql
(
  (
    sum(rate(http_requests_total{
      service="dispatch-api",
      outcome="bad"
    }[1h]))
    /
    sum(rate(http_requests_total{
      service="dispatch-api"
    }[1h]))
  ) > 0.0144
)
and
(
  (
    sum(rate(http_requests_total{
      service="dispatch-api",
      outcome="bad"
    }[5m]))
    /
    sum(rate(http_requests_total{
      service="dispatch-api"
    }[5m]))
  ) > 0.0144
)
```

Fast-burn condition: 1-hour and 5-minute bad-request ratios both above 1.44%.

The threshold `0.0144` represents a 1.44% bad-request ratio, equivalent to a 14.4× burn rate for a 99.9% SLO. The rule triggers when both observation windows exceed that threshold.

## Recording rules for the error ratios

Calculating ratios over long windows for every alert is expensive. Recording rules compute each window once and keep the `service` label, so one set of alerts can cover many services:

```yaml
groups:
  - name: slo-error-ratios
    rules:
      - record: service:error_ratio:rate5m
        expr: |
          sum by (service) (rate(http_requests_total{outcome="bad"}[5m]))
          /
          sum by (service) (rate(http_requests_total[5m]))
      - record: service:error_ratio:rate30m
        expr: |
          sum by (service) (rate(http_requests_total{outcome="bad"}[30m]))
          /
          sum by (service) (rate(http_requests_total[30m]))
      - record: service:error_ratio:rate1h
        expr: |
          sum by (service) (rate(http_requests_total{outcome="bad"}[1h]))
          /
          sum by (service) (rate(http_requests_total[1h]))
      - record: service:error_ratio:rate6h
        expr: |
          sum by (service) (rate(http_requests_total{outcome="bad"}[6h]))
          /
          sum by (service) (rate(http_requests_total[6h]))
      - record: service:error_ratio:rate3d
        expr: |
          sum by (service) (rate(http_requests_total{outcome="bad"}[3d]))
          /
          sum by (service) (rate(http_requests_total[3d]))
```

One error-ratio series per service and window.

## Alerting rules: fast, sustained and slow burn

```yaml
groups:
  - name: slo-burn-rate-alerts
    rules:
      - alert: ErrorBudgetFastBurn
        expr: |
          service:error_ratio:rate1h{service="dispatch-api"} > (14.4 * 0.001)
          and
          service:error_ratio:rate5m{service="dispatch-api"} > (14.4 * 0.001)
        labels:
          severity: critical
        annotations:
          summary: "dispatch-api is burning error budget at over 14.4x"
      - alert: ErrorBudgetSustainedBurn
        expr: |
          service:error_ratio:rate6h{service="dispatch-api"} > (6 * 0.001)
          and
          service:error_ratio:rate30m{service="dispatch-api"} > (6 * 0.001)
        labels:
          severity: critical
        annotations:
          summary: "dispatch-api is burning error budget at over 6x"
      - alert: ErrorBudgetSlowBurn
        expr: |
          service:error_ratio:rate3d{service="dispatch-api"} > (1 * 0.001)
          and
          service:error_ratio:rate6h{service="dispatch-api"} > (1 * 0.001)
        labels:
          severity: ticket
        annotations:
          summary: "dispatch-api is using error budget faster than its SLO allows"
```

Route severity: critical to on-call and severity: ticket to a ticket queue in Alertmanager.

Writing the threshold as `14.4 * 0.001` keeps the burn rate and the SLO visible in the rule. For a different SLO, change `0.001` to that SLO’s allowed error ratio: `0.0005` for 99.95%, `0.0001` for 99.99%.

## How to test burn-rate alerts

- **Unit-test the rules** with `promtool test rules`, feeding synthetic series that cross each threshold and checking the alert fires and clears.
- **Replay a real incident:** run the expressions over a past incident’s time range and confirm when each alert would have fired.
- **Check the arithmetic:** at a constant 14.4×, a 30-day budget loses 2% in one hour; confirm the fast-burn alert fires within that hour.
- **Test the quiet case:** a short spike that recovers in a minute should not fire anything.

## Common mistakes with Prometheus SLO alerts

### 1. Dividing by zero at low traffic

With no requests in a short window, the ratio is undefined and the alert may flap or go silent. Consider a minimum request count, or a synthetic check, for low-traffic services.

### 2. Counting the wrong requests as bad

The `outcome` label must match the SLI exactly. Counting client errors or health checks distorts the burn rate.

### 3. Missing telemetry looks healthy

If the metric stops being scraped, the error ratio disappears rather than rising. Add an `absent()` alert for the request counter.

### 4. Inconsistent labels

If the good and bad series carry different labels, `and` will not match them. Aggregate with the same `by (service)` clause on both sides.

## Frequently asked questions about Prometheus burn-rate alerts

### How do you calculate burn rate in PromQL?

Divide the rate of bad requests by the rate of all requests over a window, then divide by the SLO’s allowed error ratio. For a 99.9% SLO, an error ratio of 0.0144 is a burn rate of 14.4×.

### What is the PromQL threshold for a 14.4× burn rate?

For a 99.9% SLO, 14.4 × 0.001 = 0.0144, a 1.44% error ratio. For a 99.95% SLO it is 14.4 × 0.0005 = 0.0072.

### Why use recording rules for SLO alerts?

Rates over long windows such as 3 days are expensive to compute. Recording rules compute each ratio once per evaluation and store it, so alerts and dashboards query a cheap precomputed series.

### Should every burn-rate alert reach on-call?

No. Fast and sustained burns usually do, because the budget could be gone within hours or a day. Slow burns are usually routed to a ticket queue.

## Key takeaways

- A burn-rate alert compares error ratio against threshold × allowed error ratio in two windows.
- For 99.9%: 0.0144 over 1h and 5m, 0.006 over 6h and 30m, 0.001 over 3d and 6h.
- Recording rules keep long-window ratios cheap and reusable across services.
- Test with promtool, past incidents and the quiet case.

[How SteadyNines acts on burn rate](https://steadynines.com/product/how-it-works)
