In the Prometheus ecosystem, Prometheus generates alerts based on alerting rules, while Alertmanager manages the entire alert lifecycle: grouping, routing, inhibition, deduplication, and notification delivery. A poorly configured Alertmanager can drown on-call engineers in a flood of duplicate alerts at 3 AM, whereas a well-designed routing and inhibition strategy ensures that “the right person receives the right alert at the right time.”

Reference: Prometheus Official Documentation — Alertmanager

I. Alertmanager Architecture

Alertmanager’s processing pipeline consists of five stages:

Prometheus alert rule triggered
┌───────────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐
│   Receive      │ ──→ │  Group    │ ──→ │  Route    │ ──→ │  Inhibit  │ ──→ │  Dedup    │
└───────────────┘     └───────────┘     └───────────┘     └───────────┘     └─────┬─────┘
                                                                             ┌───────────────┐
                                                                             │   Notify      │
                                                                             │ Email/WeCom/  │
                                                                             │ DingTalk       │
                                                                             └───────────────┘
StagePurposeKey Config
ReceiveReceives alerts from Prometheusreceivers
GroupMerges alerts with the same characteristics into a batchgroup_by
RouteDetermines alert destination based on label matchingroute, matchers
InhibitSilences related lower-priority alerts when a higher-priority one firesinhibit_rules
DedupDeduplicates alerts across multiple Alertmanager instancesHA mode + Gossip
NotifySends notifications through configured channelswebhook / email / etc.

II. Routing Tree Design

2.1 Routing Tree Structure

Alertmanager routing is a tree. The root node is the default route, and each child node matches labels via matchers:

route:
  receiver: default
  group_by: ['alertname', 'cluster']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    # Critical alerts → immediate notification
    - matchers:
        - severity="critical"
      receiver: critical-pager
      group_wait: 0s
      repeat_interval: 1h

    # Warning level → delayed notification
    - matchers:
        - severity="warning"
      receiver: warning-channel
      group_wait: 5m
      repeat_interval: 4h
      routes:
        # Database-related warnings → DBA team
        - matchers:
            - team="dba"
          receiver: dba-team
          continue: false

    # Alert recovery notifications → unified delivery
    - matchers:
        - alertname=~"NodeDown|HostUnavailable"
      receiver: infra-team

2.2 matchers Syntax

Starting from Alertmanager v0.22, matchers uses a unified syntax that replaces the legacy match / match_re:

matchers:
  # Exact match
  - severity="critical"

  # Regex match
  - alertname=~"Node.*"

  # Not equal
  - severity!="info"

  # Negative regex match
  - instance!~"localhost.*"

  # Check label existence (value is empty)
  - "maintenance"

  # Check label absence
  - "!staging"

2.3 continue Keyword

By default, once an alert matches a child route, it won’t continue matching subsequent sibling routes. Setting continue: true allows an alert to be sent to multiple receivers simultaneously:

routes:
  - matchers:
      - severity="critical"
    receiver: pagerduty
    continue: true          # Continue matching the next route
  - matchers:
      - severity="critical"
    receiver: slack-critical

The configuration above sends critical alerts to both PagerDuty and Slack.

2.4 Cascading Route Example

routes:
  # First level: route by team
  - matchers:
      - team="sre"
    receiver: sre-default
    routes:
      # Second level: critical alerts within SRE
      - matchers:
          - severity="critical"
        receiver: sre-critical
      # Second level: disk alerts within SRE
      - matchers:
          - alertname=~"Disk.*"
        receiver: sre-storage

  - matchers:
      - team="dba"
    receiver: dba-default
    routes:
      - matchers:
          - severity="critical"
        receiver: dba-critical

III. Grouping Strategies

Grouping determines “which alerts will be merged into a single notification.”

3.1 Core Parameters

ParameterDescriptionTypical Value
group_byLabels to group by['alertname', 'cluster', 'service']
group_waitHow long to wait after the first alert before sending (waiting for more alerts in the same group)30s
group_intervalInterval between sending merged new alerts in the same group5m
repeat_intervalInterval for repeating the same alert notification4h

3.2 Grouping Timeline Example

Assuming group_by: ['alertname'], group_wait: 30s, group_interval: 5m:

t=0s    AlertA received (instance=node-1)
        → Start waiting 30s (group_wait)

t=10s   AlertA received (instance=node-2)  → Same group, merged

t=30s   Send notification [AlertA(node-1), AlertA(node-2)]
        → Start 5min timer (group_interval)

t=3min  AlertA received (instance=node-3)  → Same group, not sent yet

t=5min  Send notification [AlertA(node-3)]       → Only new additions

t=10min No new alerts, but unresolved alerts exist
        → repeat_interval not reached, no notification sent

t=4h    repeat_interval reached
        → Send repeat notification [AlertA(node-1), AlertA(node-2), AlertA(node-3)]

3.3 Grouping Configuration Recommendations

route:
  # Group by alert name + cluster
  # Same-type alerts from the same cluster are merged into one notification
  group_by: ['alertname', 'cluster', 'service']
  group_wait: 30s       # Wait 30s to collect same-group alerts
  group_interval: 5m    # Merge alerts within 5 minutes
  repeat_interval: 4h   # Don't repeat the same alert within 4 hours

Rules of thumb:

  • Do not include high-cardinality fields (like instance) in group_by, as it causes over-fragmented grouping.
  • Set group_wait to 0s for Critical alerts to send immediately.
  • Set group_wait to 5m for Warning alerts to reduce noise through merging.

IV. Inhibition Rules

The core scenario for inhibition: automatically silence related lower-priority alerts when a higher-priority alert fires.

4.1 Typical Scenarios

  • When a node goes down (NodeDown), inhibit all service alerts on that node
  • During a network partition, inhibit downstream alerts caused by network unreachability
  • When a database primary fails, inhibit application alerts that depend on that database

4.2 Configuration Example

inhibit_rules:
  # Scenario 1: When a node is down, inhibit all other alerts on that node
  - source_matchers:
      - alertname="NodeDown"
    target_matchers:
      - alertname!="NodeDown"
    equal: ['instance']

  # Scenario 2: Critical alerts inhibit Warning alerts for the same service
  - source_matchers:
      - severity="critical"
    target_matchers:
      - severity="warning"
    equal: ['alertname', 'service']

  # Scenario 3: When a cluster is unreachable, inhibit all alerts within that cluster
  - source_matchers:
      - alertname="ClusterUnavailable"
    target_matchers:
      - alertname!="ClusterUnavailable"
    equal: ['cluster']

  # Scenario 4: When DB primary is down, inhibit application alerts depending on that instance
  - source_matchers:
      - alertname="MySQLMasterDown"
    target_matchers:
      - alertname=~"App.*"
    equal: ['db_instance']

4.3 How It Works

source (NodeDown, instance=node-1, severity=critical)
                    │  Triggers inhibition
target (CPUHigh, instance=node-1, severity=warning)
                    │  equal: ['instance']
                    │  instance matches → inhibition applies
              target alert silenced

Key field descriptions:

  • source_matchers: Conditions for the alert that triggers inhibition
  • target_matchers: Conditions for the alert to be inhibited
  • equal: Source and target must have the same values for these labels to trigger inhibition

V. Silence Management

Silences are used to temporarily suppress specific alerts. A typical scenario is avoiding alert noise during planned maintenance (e.g., upgrades, restarts).

5.1 Creating a Silence via Web UI

Navigate to the Alertmanager Web UI (default http://localhost:9093) → SilencesNew Silence:

Matchers:
  alertname = NodeHighCpuLoad
  instance  = node-3

Starts At:  2026-07-10 22:00:00
Ends At:    2026-07-10 23:00:00
Created By: xubaojin
Comment:    Planned maintenance - CPU scaling

5.2 Creating a Silence via amtool CLI

amtool is the command-line tool bundled with Alertmanager:

# Create a silence (1 hour)
amtool silence add \
  --comment="Planned maintenance" \
  --author="xubaojin" \
  --duration=1h \
  alertname=NodeHighCpuLoad \
  instance=node-3

# List all silences
amtool silence query \
  --alertmanager.url=http://localhost:9093

# Query by matchers
amtool silence query instance=node-3

# Expire a silence by ID
amtool silence expire <silence-id> \
  --alertmanager.url=http://localhost:9093

# Expire all silences
amtool silence expire $(amtool silence query -o simple | awk '{print $1}')

5.3 Creating a Silence via API

curl -X POST http://localhost:9093/api/v2/silences \
  -H "Content-Type: application/json" \
  -d '{
    "matchers": [
      { "name": "alertname", "value": "NodeHighCpuLoad", "isRegex": false },
      { "name": "instance", "value": "node-3", "isRegex": false }
    ],
    "startsAt": "2026-07-10T22:00:00Z",
    "endsAt": "2026-07-10T23:00:00Z",
    "createdBy": "xubaojin",
    "comment": "Planned maintenance - CPU scaling"
  }'

5.4 amtool Common Commands Cheat Sheet

# View currently active alerts
amtool alert query \
  --alertmanager.url=http://localhost:9093

# View alert routing (check which receiver an alert matches)
amtool config routes test \
  --alertmanager.url=http://localhost:9093 \
  severity=critical team=sre

# View the routing tree (tree display)
amtool config routes show \
  --alertmanager.url=http://localhost:9093

VI. Multi-Channel Notification Configuration

6.1 Email Notifications

receivers:
  - name: email-team
    email_configs:
      - to: 'oncall@example.com'
        from: 'alertmanager@example.com'
        smarthost: 'smtp.example.com:587'
        auth_username: 'alertmanager@example.com'
        auth_password: 'YOUR_SMTP_PASSWORD'
        auth_secret: 'YOUR_SMTP_PASSWORD'
        auth_identity: 'alertmanager@example.com'
        require_tls: true
        headers:
          Subject: '{{ .Status | toUpper }} - {{ .CommonLabels.alertname }}'
        send_resolved: true

6.2 WeCom (Enterprise WeChat) Webhook

receivers:
  - name: wecom-team
    webhook_configs:
      - url: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_WECOM_KEY'
        send_resolved: true
        max_alerts: 0

The WeCom webhook requires a specific message format. You typically need to deploy an intermediary forwarding service (such as prometheus-webhook-dingtalk or a custom adapter) to convert Alertmanager’s default JSON into the WeCom format.

WeCom message template adapter example (Go):

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "bytes"

    "github.com/prometheus/alertmanager/template"
)

type WecomMessage struct {
    MsgType  string   `json:"msgtype"`
    Markdown struct {
        Content string `json:"content"`
    } `json:"markdown"`
}

func handler(w http.ResponseWriter, r *http.Request) {
    var data template.Data
    json.NewDecoder(r.Body).Decode(&data)

    content := fmt.Sprintf(
        "## Alert Notification\n"+
            "**Status**: %s\n"+
            "**Alert Count**: %d\n"+
            "**Details**:\n",
        data.Status, len(data.Alerts),
    )
    for _, alert := range data.Alerts {
        content += fmt.Sprintf(
            "- **%s**: %s\n",
            alert.Labels["alertname"],
            alert.Annotations["summary"],
        )
    }

    msg := WecomMessage{MsgType: "markdown"}
    msg.Markdown.Content = content

    payload, _ := json.Marshal(msg)
    http.Post(
        "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
        "application/json",
        bytes.NewReader(payload),
    )
    w.WriteHeader(http.StatusOK)
}

func main() {
    http.HandleFunc("/wecom", handler)
    http.ListenAndServe(":8060", nil)
}

6.3 DingTalk Webhook

receivers:
  - name: dingtalk-team
    webhook_configs:
      - url: 'http://localhost:8060/dingtalk/webhook1/send'
        send_resolved: true

DingTalk also requires message format adaptation. The recommended approach is to use the prometheus-webhook-dingtalk project:

# Start the DingTalk webhook adapter
prometheus-webhook-dingtalk \
  --ding.profile="webhook1=https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN" \
  --ding.timeout=5s

VII. Complete alertmanager.yml Configuration Example

The following is a complete configuration covering routing, grouping, inhibition, and multi-channel notifications:

global:
  resolve_timeout: 5m
  smtp_smarthost: 'smtp.example.com:587'
  smtp_from: 'alertmanager@example.com'
  smtp_auth_username: 'alertmanager@example.com'
  smtp_auth_password: 'YOUR_SMTP_PASSWORD'
  smtp_require_tls: true

# Template files (custom notification templates)
templates:
  - '/etc/alertmanager/templates/*.tmpl'

# Routing tree
route:
  receiver: default
  group_by: ['alertname', 'cluster', 'service']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    # Critical → immediate phone call + WeCom
    - matchers:
        - severity="critical"
      receiver: critical-notify
      group_wait: 0s
      repeat_interval: 1h
      continue: true

    # Warning → WeCom
    - matchers:
        - severity="warning"
      receiver: warning-notify
      group_wait: 2m

    # DBA team alerts
    - matchers:
        - team="dba"
      receiver: dba-notify
      group_wait: 1m

    # Info level → archive only, no notification
    - matchers:
        - severity="info"
      receiver: null

# Inhibition rules
inhibit_rules:
  # Node down → inhibit all other alerts on that node
  - source_matchers:
      - alertname="NodeDown"
    target_matchers:
      - alertname!="NodeDown"
    equal: ['instance']

  # Critical inhibits Warning for the same service
  - source_matchers:
      - severity="critical"
    target_matchers:
      - severity="warning"
    equal: ['alertname', 'service']

  # Cluster unreachable → inhibit all alerts in that cluster
  - source_matchers:
      - alertname="ClusterUnavailable"
    target_matchers:
      - alertname!="ClusterUnavailable"
    equal: ['cluster']

# Receiver definitions
receivers:
  - name: default
    email_configs:
      - to: 'oncall@example.com'
        send_resolved: true

  - name: critical-notify
    webhook_configs:
      - url: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_WECOM_KEY'
        send_resolved: true
    email_configs:
      - to: 'critical-oncall@example.com'
        send_resolved: true

  - name: warning-notify
    webhook_configs:
      - url: 'http://localhost:8060/dingtalk/webhook1/send'
        send_resolved: true

  - name: dba-notify
    email_configs:
      - to: 'dba@example.com'
        send_resolved: true
    webhook_configs:
      - url: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=DBA_WECOM_KEY'
        send_resolved: true

  - name: null
    # Empty receiver, discards alerts

VIII. High Availability Deployment

Alertmanager supports multi-instance HA deployment, synchronizing alert state and silence information via the Gossip protocol:

# Start a 3-node HA cluster
alertmanager \
  --config.file=/etc/alertmanager/alertmanager.yml \
  --storage.path=/var/lib/alertmanager \
  --web.listen-address=:9093 \
  --cluster.peer=alertmanager-1:9094 \
  --cluster.peer=alertmanager-2:9094 \
  --cluster.peer=alertmanager-3:9094 \
  --cluster.listen-address=0.0.0.0:9094

On the Prometheus side, configure multiple Alertmanager addresses for failover:

# prometheus.yml
alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - alertmanager-1:9093
            - alertmanager-2:9093
            - alertmanager-3:9093

Prometheus sends alerts to all Alertmanagers. The instances deduplicate via the Gossip protocol, ensuring that ultimately only one instance sends the notification.

Summary

The core of Alertmanager configuration revolves around three questions:

  1. Who should receive alerts? → Routing tree (route + matchers)
  2. How to reduce alert noise? → Grouping (group_by) + Inhibition (inhibit_rules) + Silences
  3. How are alerts delivered? → Receivers (receivers) + Notification channels

A well-crafted Alertmanager configuration should ensure that on-call engineers rarely receive alerts under normal conditions (because problems are preempted), and only receive precisely targeted, information-rich, non-duplicate alerts when anomalies occur. Remember one principle: it’s not about more alerts, it’s about the right alerts.

For more details, see Prometheus Official Documentation — Alertmanager

References & Acknowledgments

This article referenced the following materials during writing. We thank the original authors for their contributions:

  1. Prometheus Official Documentation — Alertmanager — Prometheus Authors, referenced for Prometheus Official Documentation — Alertmanager
  2. prometheus-webhook-dingtalk — GitHub, referenced for prometheus-webhook-dingtalk