An on-call rota fails long before anyone quits, at the moment the team stops reading the pages. This guide covers the three changes that keep that from happening: alerting on what a customer can feel rather than on a cause, replacing threshold pages with burn rate alerts against an objective, and grouping so that one incident produces one notification. It ends with a query that tells you which of your alerts nobody has acted on, and what to do with them.
An on-call rota does not fail when someone quits. It fails earlier, on the quiet morning when a page arrives and the person holding the phone glances at it and goes back to sleep, because the last nine were nothing. Everything below exists to keep that morning from arriving.
Page on what a customer can feel
Google's SRE material draws the line between what is broken and why it is broken, and puts the pager on the first. A disk filling up, a pod restarting or a cache miss rate climbing are all causes. They may or may not have reached anybody outside the building.
The same chapter offers a set of questions to run any candidate alert through. Does this detect an urgent, actionable condition that is affecting users and would otherwise go unnoticed. Will there be occasions when I know the alert is benign and ignore it. Does it definitely mean users are having a worse time. Can I act on it, and could that action be automated. Is somebody else already being paged for the same thing.
An alert that fails any of those is not a page. It is a dashboard panel, or a ticket for working hours, and the same material is blunt about email alerts, which drown in their own noise.
The budget is smaller than most teams assume. A human can respond to a handful of genuine emergencies in a day before fatigue sets in. Every page you send that did not need a human spends part of that.
Alert on the budget, not on the threshold
The threshold alert is the one everybody writes first. Error rate above one percent for five minutes, page. It fires during a thirty second blip that nobody noticed, and it stays silent through a week of quiet degradation that burns your entire month of allowance.
Burn rate fixes both ends. If your objective is 99.9 percent, then a sustained error rate of 0.1 percent consumes exactly the whole error budget over the window, which is a burn rate of 1. Ten times that rate exhausts it ten times faster.
The SRE workbook pairs each rate with two windows, a long one and a short one at about a twelfth of its length, and requires both to be over the threshold. The long window gives the alert meaning and the short one lets it stop firing once the problem is over. For a 99.9 percent objective over thirty days it recommends these.
| Severity | Long window | Short window | Burn rate | Budget consumed |
|---|---|---|---|---|
| Page | 1 hour | 5 minutes | 14.4 | 2% |
| Page | 6 hours | 30 minutes | 6 | 5% |
| Ticket | 3 days | 6 hours | 1 | 10% |
The percentages are worth checking rather than trusting. Thirty days is 720 hours. A burn rate of 14.4 empties the budget in 720 divided by 14.4, which is 50 hours, so one hour at that rate spends one fiftieth of it, or 2 percent. A rate of 6 empties it in 120 hours, so 6 hours is 5 percent. A rate of 1 empties it in the full 30 days, so 3 days is 10 percent.
In Prometheus, record the ratio first so the alert stays readable.
# /etc/prometheus/rules/checkout-sli.yml
groups:
- name: checkout-sli
interval: 30s
rules:
- record: job:slo_errors_per_request:ratio_rate5m
expr: |
sum by (job) (rate(http_requests_total{job="checkout",code=~"5.."}[5m]))
/
sum by (job) (rate(http_requests_total{job="checkout"}[5m]))
- record: job:slo_errors_per_request:ratio_rate1h
expr: |
sum by (job) (rate(http_requests_total{job="checkout",code=~"5.."}[1h]))
/
sum by (job) (rate(http_requests_total{job="checkout"}[1h]))
Substitute your own counter and label names. Then the alert, with the 0.001 standing for one minus the 99.9 percent objective.
# /etc/prometheus/rules/checkout-slo.yml
groups:
- name: checkout-slo
rules:
- alert: CheckoutErrorBudgetBurningFast
expr: |
job:slo_errors_per_request:ratio_rate1h{job="checkout"} > (14.4 * 0.001)
and
job:slo_errors_per_request:ratio_rate5m{job="checkout"} > (14.4 * 0.001)
for: 2m
keep_firing_for: 5m
labels:
severity: page
annotations:
summary: Checkout is spending its error budget 14.4 times too fast
runbook: https://example.com/runbooks/checkout-errors
for holds the alert in a pending state until the condition has been true throughout that period, which kills the blips. keep_firing_for, available since Prometheus 2.42, keeps it firing for a while after the condition clears, which kills the flapping and the false all-clear. Without it an alerting rule deactivates on the first evaluation where the condition is not met.
Check the file before it goes anywhere near a reload.
promtool check rules /etc/prometheus/rules/checkout-slo.yml
One incident, one notification
Prometheus decides what is wrong and Alertmanager decides who hears about it. Grouping is the part that turns forty simultaneous alerts into one message. group_by collects alerts sharing the labels you name, group_wait holds the first notification for 30 seconds by default so that stragglers join the same group, group_interval waits 5 minutes before sending an update about a group that has changed, and repeat_interval waits 4 hours before saying the same thing again.
Inhibition is the other half. An inhibit rule mutes a target alert while a source alert is firing, with both required to share the values of the labels listed under equal. A cluster wide outage suppressing the fifty service alerts underneath it is the standard use, and it is the difference between one page and a phone that will not stop.
Retire the alerts nobody acts on
Alerts accumulate. Nobody deletes them, because deleting one feels like accepting risk. So measure them instead. Prometheus writes a synthetic ALERTS series for every active alert, labelled with the alert name and whether it is pending or firing.
sort_desc(
sum by (alertname) (count_over_time(ALERTS{alertstate="firing"}[30d]))
)
That ranks alerts by how much of the last thirty days each spent firing, counted in evaluation samples rather than in distinct incidents. The names at the top are the ones your team has learned to ignore.
Take each one and ask what a human did the last three times it fired. If the answer is nothing, the alert has already been retired in practice and the only honest move is to make that official. Send it to a ticket queue, turn it into a dashboard panel, or delete it. If the answer is that somebody always runs the same command, that is not an alert either, it is a script waiting to be written.
Doing this once a quarter costs an hour and is the only thing that keeps a rota trustworthy. If you would rather have the alerting rebuilt around objectives than pruned in place, that is our monitoring and observability work, and the practice around it, incident response and postmortems included, sits in our SRE service.
Frequently asked questions
Should on-call alerts page on symptoms or on causes?
Page on symptoms, meaning what a customer can feel. Google's SRE material separates what is broken from why it is broken and puts the pager on the first. A disk filling up, a pod restarting or a climbing cache miss rate are causes that may or may not have reached anybody outside the building. A human can respond to only a handful of genuine emergencies a day before fatigue sets in, and every page that did not need a human spends part of that.
How do I decide whether an alert should page a human?
An alert earns a page when it detects an urgent, actionable condition that is affecting users and would otherwise go unnoticed, and definitely means users are having a worse time. It should never be known to be benign and ignored. Someone must be able to act on it, the action should not be one that could be automated, and nobody else should already be paged for it. Anything that fails these tests belongs on a dashboard panel or in a ticket for working hours.
What is an error budget burn rate alert?
Burn rate measures how fast a service spends its error budget. With a 99.9 percent objective, a sustained 0.1 percent error rate consumes exactly the whole budget over the window, a burn rate of 1, and ten times that rate exhausts it ten times faster. A plain threshold such as error rate above one percent for five minutes fires on a thirty second blip and stays silent through a week of quiet degradation, and burn rate alerting fixes both ends.
What burn rate thresholds should page for a 99.9 percent SLO?
For a 99.9 percent objective over thirty days, the SRE workbook recommends paging when the burn rate exceeds 14.4 over 1 hour and 5 minutes, or 6 over 6 hours and 30 minutes, and opening a ticket at a burn rate of 1 over 3 days and 6 hours. Those spend 2, 5 and 10 percent of the budget. Each long window is paired with a short one about a twelfth of its length, and both must exceed the threshold, so the alert means something and stops once the problem is over.
Why does a 14.4 burn rate over one hour use 2 percent of the error budget?
Thirty days is 720 hours, and a burn rate of 14.4 empties the budget in 720 divided by 14.4, which is 50 hours. One hour at that rate therefore spends a fiftieth of the budget, or 2 percent. By the same arithmetic a burn rate of 6 empties it in 120 hours, so 6 hours spends 5 percent, and a burn rate of 1 over 3 days spends 10 percent.
What do for and keep_firing_for do in Prometheus alerting rules?
The for clause holds an alert in a pending state until its condition has been true for the whole period, which filters out blips. The keep_firing_for setting, available since Prometheus 2.42, keeps the alert firing for a while after the condition clears, which prevents flapping and false all-clears. Without it, an alerting rule deactivates on the first evaluation where the condition is not met. Validate rule files with promtool check rules before any reload.
How do I stop Alertmanager from sending many notifications for one incident?
Alertmanager grouping collects alerts that share the labels named in group_by into one message. By default group_wait holds the first notification for 30 seconds so stragglers join, group_interval waits 5 minutes before sending an update about a changed group, and repeat_interval waits 4 hours before repeating. Inhibit rules mute target alerts while a source alert fires, as long as both share the labels listed under equal, so one cluster wide outage can suppress the fifty service alerts beneath it.
How do I find Prometheus alerts that nobody acts on?
Prometheus writes a synthetic ALERTS series for every active alert, so summing count_over_time(ALERTS{alertstate="firing"}[30d]) by alertname and sorting it ranks alerts by how much of the last thirty days each spent firing, counted in evaluation samples. For each name near the top, ask what a human did the last three times it fired. If the answer is nothing, send it to a ticket queue, make it a dashboard panel or delete it. If someone always runs the same command, write the script.
Read how we handle it in DevOps as a Service.
Related Articles
How to Tune a Linux Server That Falls Over Only at Peak
Load average under one, CPU half idle, and for four minutes every evening the site returns errors. The limits that bite at peak are queues and counters rather than utilisation, which is why no dashboard you own is measuring them. This guide shows where to read the accept queue overflow counter, the PHP-FPM log lines that name the pool ceiling in plain words, the ephemeral port range, and the connection tracking table, along with the sysctl and nginx directives that raise each one. Every observation command comes before the change it justifies.
Server & DevOpsYour Images Are Probably Not What Makes LCP Slow
Cloudflare's BEACON data from 10,000 large sites shows where slow LCP comes from. On pages rated poor, downloading the LCP resource took 119 ms, while the first byte, late discovery and blocked rendering each took one and a half to two seconds.
Server & DevOpsHow to Troubleshoot and Fix the "Error Establishing a Database Connection" in WordPress
An in-depth troubleshooting guide for the WordPress database connection error, covering wp-config.php verification, password resets, database repair, MySQL service management, and preventive measures.