ObservabilityXXIII · Grafana FoundationsGrafanaFoundations
Alerting in Grafana
What you'll learn
- Locate alert rules, contact points, and notification policies in the unified Grafana alerting model
- Provision alerting rules from YAML so the rule definition is version-controlled
- Distinguish Grafana-managed alerting from Prometheus-managed alerting and decide which to use for an SLO rule
- Use the /api/v1/provisioning/alert/* endpoints to validate a deployed rule and contact point
Prerequisites
Verified against Prometheus 2.55.x · Alertmanager 0.28.x · node_exporter 1.8.x · blackbox_exporter 0.26.x · Grafana 11.x · Loki 3.x · Tempo current · OpenTelemetry Collector 0.110.x · Grafana Alloy current · Docker Engine 28.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-13
A paged alert at 02:00 reads “CPU above 80%”. The on-call engineer opens the rule and the message is the message Grafana sent; the notification policy is what decided how the message was routed; the contact point is the integration that delivered it. Knowing which of those three to edit is the difference between a five-minute fix and a five-hour raid.
This lesson is about the three pieces: rules (the what), notification policies (the who and when), and contact points (the where). It closes with the boundary with Prometheus Alertmanager — the place where most Grafana alert teams go wrong.
What alerting in Grafana is
Grafana Alerting is the in-Grafana alerting subsystem, separate
from Prometheus’s /alertmanager and from any externally run
Alertmanager. It is “unified” in that the same alert rule can be
expressed as a Prometheus query, a Loki query, or a Tempo query
behind the same rule model. Three objects make up the subsystem:
+--- Rule group (file on disk or in storage) ------------------+
| |
| +--- Rule ------- +--- Rule ------- +--- Rule -----+ |
| | expr | expr | expr | |
| | for: 5m | for: 10m | for: 2m | |
| | noDataState: | execErrState: | labels: | |
| | NoData | Alerting | team: sre | |
| +-----------------+-----------------+-----------------+ |
+--------------------------------------------------------------+
|
v
+--- Notification policy (storage) ----------------------------+
| |
| matchers: team=sre -> contact point "sre-pager"|
| matchers: severity=high -> contact point "war-room" |
| fallback: -> contact point "default" |
| |
+--------------------------------------------------------------+
|
v
+--- Contact point (integration) ------------------------------+
| |
| types: webhook | pagerduty | slack | email | teams | opsgenie|
| settings: URL, channel, API key stored in secureJsonData |
| |
+--------------------------------------------------------------+
A rule evaluates a query on its interval, classifies the result
as one of Normal, Pending, Alerting, No Data, or Error,
and (for the Alerting and Error cases) emits a notification.
The notification policy decides which contact point receives the
notification, and silences or inhibition modify the flow.
Why a sysadmin cares
Alerting failure shapes map directly to money and reputation:
- Alert rule is firing but no one is paged. Caused by a missing or mis-routed contact point, or a notification policy that suppresses everything.
- Alert is paging but the on-call does not know what to do. The notification template is silent on what the rule means. The fix is template work, not contact-point work.
- Alert fires only after the incident is over. A
for:that is too long for the SLO and anoDataStatethat is set toOKinstead ofNoData. - Alert flaps every minute. A near-threshold expression and
a short evaluation interval cause perpetual state-changing
notifications. The fix is
for:plus akeep_firing_for.
These are ticket templates. The lesson is the diagnostic path between the symptom and the field.
How it works
The evaluation engine is a Go service inside the Grafana process. The path from a query to a paged human looks like this:
every interval every interval
+--------------+ +-------------------+
| rule group |-----------> | eval engine |
| (yaml or | turn-on | |
| storage) | | query datasource |
+--------------+ | -> data source |
| -> classification |
+-------------------+
|
classifier (Normal/Pending/Alerting/NoData/Error)
|
v
+-------------------+
| state store |
| + active rules |
| + silences |
| + history |
+-------------------+
|
v
+-------------------+
| notification |
| policy + matcher |
+-------------------+
|
v
+-------------------+
| contact points |
| (pagerduty, slack, |
| teams, opsgenie) |
+-------------------+
Three numbers characterise every rule:
- interval — how often the rule evaluates the query. The default of 1m is fine for SLI / SLO rules; some high-frequency rules drop to 30s. Once below 10s, the rule is system-induced.
- for — how long the state must remain
Alertingbefore a notification is emitted. Afor: 5magainst a transient spike does not page; afor: 0swould. - keep_firing_for — for how long the rule continues to
notify after the state goes back to
Normal. Use this to guarantee a single notification per incident, not per state-change.
How to configure it
Three provisioning files describe the complete alerting landscape:
groups of rules, the notification policy, and the contact points.
This is the file layout Grafana expects under
/etc/grafana/provisioning/alerting/.
# /etc/grafana/provisioning/alerting/contact_points.yaml
apiVersion: 1
contactPoints:
- orgId: 1
name: sre-pager
receivers:
- uid: pd-sre
type: pagerduty
settings:
# The integration key lives in secureJsonData so the
# plaintext form does not appear in provisioning diffs.
service: SRE primary
severity: critical
secureSettings:
integrationKey: ${PAGERDUTY_INTEGRATION_KEY_SRE}
- orgId: 1
name: default
receivers:
- uid: slack-default
type: slack
settings:
url: https://hooks.slack.com/services/${SLACK_TID_SRE}/${SLACK_TOKEN_SRE}
channel: #sre-alerts
# /etc/grafana/provisioning/alerting/routes.yaml
apiVersion: 1
routes:
- receiver: sre-pager
group_by: [grafana_folder, alertname]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
object_matchers:
- [team, "=", sre]
- [severity, "=", critical]
- receiver: default
object_matchers:
- [severity, "=", info]
# /etc/grafana/provisioning/alerting/rules.yaml
apiVersion: 1
groups:
- orgId: 1
name: sre-error-rate
folder: SRE
interval: 1m
rules:
- uid: high-error-budget-burn
title: High error budget burn on checkout
condition: C
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: prom-prod
model:
refId: A
expr: |
sum(rate(checkout_requests_total{status=~"5.."}[5m]))
/
sum(rate(checkout_requests_total[5m]))
>
0.01
instant: true
intervalMs: 1000
maxDataPoints: 43200
- refId: B
datasourceUid: __expr__
model:
refId: B
type: threshold
conditions:
- evaluator: { type: gt, params: [0.001] }
operator: { type: and }
query: { params: [A] }
reducer: { type: last }
for: 2m
keep_firing_for: 5m
noDataState: OK
execErrState: Alerting
annotations:
summary: "checkout error rate above 1% for 2m"
runbook_url: https://runbooks.example/checkout-error-rate
description: "Error rate = {{ $value | humanizePercentage }}"
labels:
team: sre
severity: critical
Three things to read in this YAML:
- The
model.expris the expression.instant: trueproduces a single value; anintervalquery produces a series. The threshold type is hidden inside the secondrefId(a server-side expression). noDataState: OKis the dangerous default for SLO rules; setting it toNoDatamakes a missing data source emit a notification (this is what you want for critical rules).runbook_urlis the field the notification templates typically surface; populate it consistently.
How to validate it
Five checks confirm the alerting subsystem end-to-end.
Severity: READ-ONLY unless noted.
# 1. The alertmanager's view of itself.
curl -sf -u "grafana-admin:$GF_ADMIN_PASSWORD" \
http://grafana:3000/api/v1/provisioning/alertmanager/config | jq
{
"alertmanager_config": {
"org_id": 1,
"alertmanager_name": "grafana",
"default": "default"
}
}
# 2. The contact-point list.
curl -sf -u "grafana-admin:$GF_ADMIN_PASSWORD" \
http://grafana:3000/api/v1/provisioning/contact-points | jq
[
{ "uid": "pd-sre", "name": "sre-pager", "type": "pagerduty" },
{ "uid": "slack-default", "name": "default", "type": "slack" }
]
# 3. The notification policy.
curl -sf -u "grafana-admin:$GF_ADMIN_PASSWORD" \
http://grafana:3000/api/v1/provisioning/folder/1/rule-groups | jq
# 4. The rule group (and per-rule UID).
curl -sf -u "grafana-admin:$GF_ADMIN_PASSWORD" \
http://grafana:3000/api/v1/provisioning/folder/1/rule-groups/sre-error-rate \
| jq '.rules[] | {uid, title, for}'
{
"uid": "high-error-budget-burn",
"title": "High error budget burn on checkout",
"for": "2m"
}
# 5. The state of a known rule.
# RULE_UID is the uid reported by the rule group listing in step 4.
RULE_UID=high-error-budget-burn
curl -sf -u "grafana-admin:$GF_ADMIN_PASSWORD" \
"http://grafana:3000/api/v1/provisioning/alert-rules/$RULE_UID/state"
{ "state": "inactive", "lastEvaluated": "2026-08-13T16:00:00Z" }
A rule with state: active and no notification has only one
explanation: the notification policy matches, but the contact
point’s secret is empty or the integration endpoint is
unreachable. Inspect the [Alerting] block of the Grafana log
for the relevant error.
How it can fail
Six failure modes that recur in real fleets:
noDataStatemis-set. A rule withnoDataState: OKflattens to “no notification” when the data source stops responding. The cluster appears to recover; the alert does not fire. SetnoDataState: NoDatafor SLO rules.- Contact-point secret missing. A PagerDuty contact point
with an empty
integrationKeysilently fails to deliver. The state is normal; the notification policy matched; the delivery failed. Inspect the log lineNotifier notifier=pagerduty returned an error. - Notification policy does not match. The rule has the
correct labels; the route’s
object_matchersdoes not include this rule; the fallback fires only if a fallback route exists. Without one, the notification is dropped. - Grafana and Prometheus alerting both firing. A duplicated rule produces two notifications for the same condition. The on-call is paged twice. Disable one of the two.
evaluation_intervaltoo long for the SLO. A 5-minute interval against a 5-minute error-budget burn window detects the burn too late. Lower the interval or move to a Prometheus recording rule.keep_firing_forproduces a notification gap. A 5-minutekeep_firing_forafter the alert returns toNormal. If the next spike starts within the window, no second notification is emitted. Tune the value or remove it.
How to troubleshoot it
The order works because each step eliminates a layer.
- Does the rule evaluate? The rule state in
/api/v1/provisioning/alert-rules/<uid>/stateshows the last evaluation result.lastEvaluated: nevermeans the rule evaluator was not scheduled; check the Grafana log. - Is the query firing? Use the same query in Explore and
watch for the condition value. A rule over a label the data
source does not emit always evaluates to “empty” with
noDataState. - Does the notification policy match? Read the rules in
/api/v1/provisioning/folder/1/rule-groups/<group>and check the rule’slabelsagainst the route’sobject_matchers. A label-as-string mismatch (team=srevsteam="sre") is a common fault. - Did the contact point deliver? Check
/api/v1/provisioning/contact-pointsand the[Alerting]block of the Grafana log forNotifier notifier=<type> returned an error. - Is the integration endpoint healthy? From the Grafana
host,
curl -ithe integration URL with thesecureSettingssubstituted. PagerDuty’s Events API/v2/enqueuereturns a202 Acceptedon success; Slack’s webhook returns a200 OKwith empty body. - Is the rate-limiter silencing? Add
repeat_interval30s instead of 4h in the route, watch the next notification, restore the larger value once delivered.
Security implications
- Notification channels leak data. A PagerDuty alert with a full notification payload including customer IDs is a regulated-data disclosure via a non-regulated channel. Template the notification to strip or hash identifiers.
- Contact-point secrets are stored in
secureJsonDataand encrypted withsecret_key. They are reversible by the server, not by a vault. Treat them as host-resident secrets. Use the integration secrets PagerDuty / Slack / OpsGenie provision per environment; do not re-use the production PagerDuty key in staging. - Webhook endpoints. Each webhook contact point is an outbound request from Grafana to a URL. The URL is part of the configuration. It is not an authenticated server and any compromise of the contact-point URL or its secret is a path to a forged notification. Validate URL ownership in the integration setup.
- OTLP / external Alertmanager. If Grafana’s alertmanager
URL is pointed at an external Alertmanager for legacy
setups, the URL is in
[unified_alerting]and the connection is auth-stripped. Network-segment Grafana from Alertmanager with the same isolation as a database.
Performance implications
- A rule group with a slow query holds the evaluator thread.
Move
exprcomputations to recording rules for a hot path. - A high-frequency interval (sub-30s) wastes both CPU and data-source bandwidth. The rule engine has no built-in deduplication across rules; if two rules share an expression, define a recording rule and reference it.
- The state store is bounded by the number of currently-firing alerts, not the total number of rules. A Grafana with 10,000 rules and 50 firing alerts uses the same memory as one with 50 rules and 50 firing alerts.
Production guidance
- Adopt provisioning for every alert definition that is part of an SLO. UI-created rules are version-control-hostile; their drift from the team’s intent is hard to spot.
- Set
noDataStateon every rule. The default (OK) is almost never right for SLO rules; the discipline is to be explicit. - Maintain a notification policy that is small, named, and
reviewed. A sprawling
matcherstree is the source of the “notification went nowhere” tickets. - Pre-wire
[Alerting].secondary_resourceto an S3 bucket or similar for the alertmanager state file. A state-store corruption on the primary node is otherwise a rule-history loss. - Define
keep_firing_forper SLO family. The default of0is fine for paging alerts; longer windows suit SLO notifications where the operator is expected to investigate during the burn.
Verification
You should now be able to answer:
- What are the three objects the unified alerting model owns?
- What does
noDataState: OKdo for a missing-data signal? - What is the difference between a notification policy and a contact point?
- When is Grafana-managed alerting the wrong choice and Prometheus the right one?
- Which API endpoint lists the contact points and which lists the rule groups?
Quiz
Knowledge check · 8 questions
Q1. What three objects make up the unified Grafana alerting model?
Q2. What does noDataState: OK do for a critical SLO rule?
Q3. keep_firing_for in a rule prevents the alert from emitting notifications after it returns to Normal.
Q4. Where in the alerting model does the decision of which contact point receives the notification live?
Q5. Name one field in a rule definition that decides how long the rule must be in the Alerting state before a notification fires.
Q6. Which of the following are common causes of "alert is firing but no one is being paged"?
Q7. Which routing-tree property decides what happens when no rule label matches any policy route?
Q8. Which alert-engine choice is correct for an SLO rule on a Prometheus metric whose query is shared across teams?
Passing score: 75%. Answers are checked in this browser.