How To

Prometheus Alertmanager Setup – Slack, Email, PagerDuty

Alertmanager handles alert deduplication, grouping, silencing, and routing for Prometheus. It takes alerts from Prometheus and sends notifications to the right people through the right channels – email, Slack, PagerDuty, Microsoft Teams, and more. This guide covers a full Alertmanager setup with real-world routing configurations on both Ubuntu and Rocky Linux.

Original content from computingforgeeks.com - post 164045

How Does Alertmanager Work?

Prometheus evaluates alert rules and sends firing alerts to Alertmanager. From there, Alertmanager does the heavy lifting:

  • Grouping – Combines related alerts into a single notification (e.g., all InstanceDown alerts in one message)
  • Deduplication – Prevents the same alert from being sent repeatedly
  • Routing – Directs alerts to different receivers based on labels (severity, team, service)
  • Inhibition – Suppresses less important alerts when a more critical one is firing
  • Silencing – Temporarily mutes specific alerts during maintenance windows

For more details on the architecture, see the official Alertmanager documentation.

Prerequisites

  • A running Prometheus 3 instance – see Install Prometheus 3 on Ubuntu / Debian
  • Ubuntu 24.04/Debian 13 or Rocky Linux 10/AlmaLinux 10
  • SMTP credentials for email alerts (Gmail, Mailgun, or any SMTP server)
  • Slack workspace with webhook URL (for Slack integration)

Step 1: Install Alertmanager

Create a dedicated user for the Alertmanager service:

Ubuntu/Debian:

sudo useradd --no-create-home --shell /bin/false alertmanager

Rocky/AlmaLinux:

sudo useradd --no-create-home --shell /sbin/nologin alertmanager

Create the configuration and data directories:

sudo mkdir -p /etc/alertmanager /var/lib/alertmanager
sudo chown alertmanager:alertmanager /var/lib/alertmanager

Download and install the latest Alertmanager release:

VER=$(curl -sI https://github.com/prometheus/alertmanager/releases/latest | grep -i ^location | grep -o v[0-9.]* | sed s/^v//)
echo "Installing Alertmanager $VER"
wget https://github.com/prometheus/alertmanager/releases/download/v${VER}/alertmanager-${VER}.linux-amd64.tar.gz
tar xvf alertmanager-${VER}.linux-amd64.tar.gz
sudo cp alertmanager-${VER}.linux-amd64/{alertmanager,amtool} /usr/local/bin/
sudo chown alertmanager:alertmanager /usr/local/bin/{alertmanager,amtool}

At the time of writing, this installs Alertmanager 0.31.1. Verify the installation:

alertmanager --version

You should see the version and build info:

alertmanager, version 0.31.1 (branch: HEAD, revision: e8d9fba4)
  build user:       root@71dc7c3e3a06
  build date:       20260228-14:18:37
  go version:       go1.24.0
  platform:         linux/amd64

On Rocky/AlmaLinux, set the SELinux context for the binaries:

sudo dnf install -y policycoreutils-python-utils
sudo semanage fcontext -a -t bin_t "/usr/local/bin/alertmanager"
sudo semanage fcontext -a -t bin_t "/usr/local/bin/amtool"
sudo restorecon -v /usr/local/bin/{alertmanager,amtool}
sudo semanage port -a -t http_port_t -p tcp 9093

Step 2: Configure Email Alerts (SMTP)

Email is the most common notification channel. The Alertmanager configuration uses YAML and supports multiple notification integrations in a single file.

sudo vi /etc/alertmanager/alertmanager.yml

Start with a basic email configuration. Replace the SMTP settings with your actual mail server credentials:

global:
  resolve_timeout: 5m
  smtp_smarthost: 'smtp.gmail.com:587'
  smtp_from: '[email protected]'
  smtp_auth_username: '[email protected]'
  smtp_auth_password: 'your-app-password'
  smtp_require_tls: true

templates:
  - '/etc/alertmanager/templates/*.tmpl'

route:
  receiver: 'email-notifications'
  group_by: ['alertname', 'instance']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h

  routes:
    - match:
        severity: critical
      receiver: 'critical-email'
      repeat_interval: 1h

    - match:
        severity: warning
      receiver: 'email-notifications'
      repeat_interval: 4h

receivers:
  - name: 'email-notifications'
    email_configs:
      - to: '[email protected]'
        send_resolved: true

  - name: 'critical-email'
    email_configs:
      - to: '[email protected]'
        send_resolved: true
        headers:
          Subject: '[CRITICAL] {{ .GroupLabels.alertname }} firing on {{ .GroupLabels.instance }}'

Key settings to understand:

  • group_wait: 30s – Wait 30 seconds for related alerts to arrive before sending the first notification
  • group_interval: 5m – Wait 5 minutes before sending updates about new alerts in the same group
  • repeat_interval: 4h – Don’t re-send the same alert more often than every 4 hours
  • send_resolved: true – Send a notification when an alert resolves (goes back to normal)

For Gmail, you’ll need to generate an App Password in your Google Account settings. Regular passwords won’t work with SMTP if 2FA is enabled.

Step 3: Add Slack Integration

Slack is ideal for team-visible alerts. Create an incoming webhook in your Slack workspace under Apps > Incoming Webhooks, then add a Slack receiver to the configuration.

Update the receivers section in /etc/alertmanager/alertmanager.yml to include Slack:

receivers:
  - name: 'email-notifications'
    email_configs:
      - to: '[email protected]'
        send_resolved: true

  - name: 'critical-email'
    email_configs:
      - to: '[email protected]'
        send_resolved: true

  - name: 'slack-notifications'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX'
        channel: '#alerts'
        title: '{{ .GroupLabels.alertname }}'
        text: >-
          {{ range .Alerts }}
          *Alert:* {{ .Annotations.summary }}
          *Description:* {{ .Annotations.description }}
          *Severity:* {{ .Labels.severity }}
          {{ end }}
        send_resolved: true
        color: '{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}'

  - name: 'slack-critical'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX'
        channel: '#critical-alerts'
        title: 'CRITICAL: {{ .GroupLabels.alertname }}'
        text: >-
          {{ range .Alerts }}
          *Alert:* {{ .Annotations.summary }}
          *Instance:* {{ .Labels.instance }}
          *Description:* {{ .Annotations.description }}
          {{ end }}
        send_resolved: true

Step 4: Add Microsoft Teams Webhook

Microsoft Teams supports incoming webhooks through Power Automate workflows (the old Office 365 Connector method is being deprecated). Create a workflow that accepts an HTTP POST and posts to a Teams channel, then use the webhook URL in Alertmanager.

Add a Teams receiver using the webhook_configs integration:

  - name: 'teams-notifications'
    webhook_configs:
      - url: 'https://prod-XX.westus.logic.azure.com:443/workflows/XXXXX/triggers/manual/paths/invoke?api-version=2016-06-01'
        send_resolved: true
        http_config:
          follow_redirects: true

For richer formatting in Teams, you’ll need an intermediate webhook relay that transforms the Alertmanager JSON payload into an Adaptive Card format. The prometheus-msteams project handles this conversion.

Step 5: Add PagerDuty Integration

PagerDuty is the standard for on-call alerting. Create a Prometheus integration in PagerDuty to get a routing key, then add this receiver:

  - name: 'pagerduty-critical'
    pagerduty_configs:
      - routing_key: 'your-pagerduty-events-v2-routing-key'
        severity: '{{ .GroupLabels.severity }}'
        description: '{{ .GroupLabels.alertname }}: {{ .CommonAnnotations.summary }}'
        details:
          firing: '{{ .Alerts.Firing | len }}'
          resolved: '{{ .Alerts.Resolved | len }}'
          instance: '{{ .GroupLabels.instance }}'

PagerDuty uses the Events API v2 with routing keys (not service keys from v1). Get the routing key from Services > Service Directory > Your Service > Integrations > Events API v2.

Step 6: Configure Severity-Based Route Tree

The real power of Alertmanager is its routing tree. Here’s a production-ready route configuration that sends critical alerts to PagerDuty and Slack, warnings to email, and informational alerts to Slack only:

route:
  receiver: 'email-notifications'
  group_by: ['alertname', 'cluster', 'service']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h

  routes:
    - match:
        severity: critical
      receiver: 'pagerduty-critical'
      continue: true
      repeat_interval: 30m

    - match:
        severity: critical
      receiver: 'slack-critical'
      repeat_interval: 1h

    - match:
        severity: warning
      receiver: 'slack-notifications'
      repeat_interval: 4h

    - match_re:
        alertname: '^(Watchdog|InfoInhibitor)$'
      receiver: 'null'

receivers:
  - name: 'null'

  - name: 'email-notifications'
    email_configs:
      - to: '[email protected]'
        send_resolved: true

  - name: 'pagerduty-critical'
    pagerduty_configs:
      - routing_key: 'your-pagerduty-routing-key'

  - name: 'slack-critical'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/XXXXX'
        channel: '#critical-alerts'
        send_resolved: true

  - name: 'slack-notifications'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/XXXXX'
        channel: '#alerts'
        send_resolved: true

Notice the continue: true on the first critical route. This tells Alertmanager to keep matching after this route, so critical alerts go to both PagerDuty AND Slack. Without continue, routing stops at the first match.

The null receiver is a common pattern for dropping alerts you don’t want notifications for, like the Watchdog heartbeat alert from kube-prometheus-stack.

Step 7: Configure Inhibition Rules

Inhibition rules suppress less important alerts when a related critical alert is firing. This prevents alert floods – when a server is down, you don’t need separate notifications for every service on that server.

Add this to your alertmanager.yml:

inhibit_rules:
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['alertname', 'instance']

  - source_match:
      alertname: 'InstanceDown'
    target_match_re:
      alertname: '.+'
    equal: ['instance']

The first rule says: if a critical alert is firing, suppress any warning alerts with the same alertname and instance. The second rule suppresses all alerts for an instance when InstanceDown is firing – because if the machine is down, alerting on high CPU or low disk on that same machine is noise.

Step 8: Create Custom Notification Templates

The default Alertmanager notification format is functional but verbose. Custom templates let you control exactly what information appears in each notification and how it’s formatted.

Create the templates directory and a custom email template:

sudo mkdir -p /etc/alertmanager/templates
sudo vi /etc/alertmanager/templates/email.tmpl

Add a clean, production-friendly email template:

{{ define "email.custom.subject" }}
[{{ .Status | toUpper }}] {{ .GroupLabels.alertname }} ({{ .Alerts | len }} alerts)
{{ end }}

{{ define "email.custom.html" }}
<h2>{{ .GroupLabels.alertname }}</h2>
<p>Status: <strong>{{ .Status | toUpper }}</strong></p>
<table border="1" cellpadding="5">
  <tr><th>Instance</th><th>Severity</th><th>Summary</th></tr>
  {{ range .Alerts }}
  <tr>
    <td>{{ .Labels.instance }}</td>
    <td>{{ .Labels.severity }}</td>
    <td>{{ .Annotations.summary }}</td>
  </tr>
  {{ end }}
</table>
{{ end }}

Reference the template in your email receiver config by adding html and headers fields:

  - name: 'email-notifications'
    email_configs:
      - to: '[email protected]'
        send_resolved: true
        html: '{{ template "email.custom.html" . }}'
        headers:
          Subject: '{{ template "email.custom.subject" . }}'

Templates use Go’s text/template syntax. The most common variables are .Status, .GroupLabels, .CommonLabels, .CommonAnnotations, and .Alerts (which you iterate with range). Each alert inside .Alerts has .Labels, .Annotations, .StartsAt, and .EndsAt.

Step 9: Create the Systemd Service

Create the systemd unit file for Alertmanager:

sudo vi /etc/systemd/system/alertmanager.service

Add the following service definition:

[Unit]
Description=Prometheus Alertmanager
Documentation=https://prometheus.io/docs/alerting/latest/alertmanager/
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=alertmanager
Group=alertmanager
ExecStart=/usr/local/bin/alertmanager \
  --config.file=/etc/alertmanager/alertmanager.yml \
  --storage.path=/var/lib/alertmanager \
  --web.listen-address=0.0.0.0:9093 \
  --cluster.listen-address=""
Restart=always
RestartSec=5
SyslogIdentifier=alertmanager

[Install]
WantedBy=multi-user.target

The --cluster.listen-address="" flag is critical for single-node deployments, especially on cloud VMs. Without it, Alertmanager tries to find a private IP for its gossip protocol and fails with “no private IP found” on many cloud providers. If you’re running a multi-node Alertmanager cluster, remove this flag and use --cluster.peer instead.

Set ownership on the configuration file and start the service:

sudo chown alertmanager:alertmanager /etc/alertmanager/alertmanager.yml
sudo systemctl daemon-reload
sudo systemctl enable --now alertmanager

Verify the service is running:

sudo systemctl status alertmanager

The service should show active (running):

● alertmanager.service - Prometheus Alertmanager
     Loaded: loaded (/etc/systemd/system/alertmanager.service; enabled; preset: enabled)
     Active: active (running) since Mon 2026-03-24 11:05:32 UTC; 4s ago
       Docs: https://prometheus.io/docs/alerting/latest/alertmanager/
   Main PID: 4521 (alertmanager)
      Tasks: 7 (limit: 4557)
     Memory: 18.4M
        CPU: 0.234s
     CGroup: /system.slice/alertmanager.service
             └─4521 /usr/local/bin/alertmanager --config.file=/etc/alertmanager/alertmanager.yml ...

Step 9: Open Firewall Ports

Alertmanager listens on port 9093. Open it in your firewall.

Ubuntu/Debian (UFW):

sudo ufw allow 9093/tcp comment "Alertmanager"
sudo ufw reload

Rocky/AlmaLinux (firewalld):

sudo firewall-cmd --permanent --add-port=9093/tcp
sudo firewall-cmd --reload

Step 10: Access the Alertmanager Web UI

Open your browser and navigate to http://your-server-ip:9093. The Alertmanager UI shows active alerts, silences, and the current routing configuration:

Alertmanager web UI showing alert groups, active alerts, and silence management interface
Alertmanager web UI with alert groups and silence management

How to Manage Silences with amtool

Silences temporarily mute alerts during maintenance windows. The amtool CLI makes managing silences easy without touching the web UI.

First, configure amtool to know where Alertmanager is running:

sudo mkdir -p /etc/amtool
echo "alertmanager.url: http://localhost:9093" | sudo tee /etc/amtool/config.yml

Create a silence for a specific alert during a maintenance window:

amtool silence add alertname=InstanceDown instance="10.0.1.20:9100" --duration=2h --comment="Scheduled maintenance on web-02"

The command returns a silence ID that you can use to manage it:

4b5e8f2a-1c3d-4e5f-9a8b-7c6d5e4f3a2b

List all active silences:

amtool silence query

You’ll see a table of all active silences with their matchers and expiry times:

ID                                    Matchers                                          Ends                  Created By  Comment
4b5e8f2a-1c3d-4e5f-9a8b-7c6d5e4f3a2b  alertname=InstanceDown instance=10.0.1.20:9100    2026-03-24 13:05:32   admin       Scheduled maintenance on web-02

Expire a silence before its scheduled end time:

amtool silence expire 4b5e8f2a-1c3d-4e5f-9a8b-7c6d5e4f3a2b

You can also query the current alert status:

amtool alert query

How to Validate Your Configuration

Always validate the Alertmanager config before restarting the service. A bad config will prevent Alertmanager from starting, which means no alerts get delivered.

amtool check-config /etc/alertmanager/alertmanager.yml

A successful validation returns:

Checking '/etc/alertmanager/alertmanager.yml'  SUCCESS
Found:
 - global config
 - route
 - 1 inhibit rules
 - 5 receivers
 - 0 templates

How to Debug Alert Routing

When alerts aren’t reaching the expected receiver, use amtool to test which route an alert would match:

amtool config routes test --config.file=/etc/alertmanager/alertmanager.yml severity=critical alertname=InstanceDown

This shows the receiver that would handle an alert with those labels:

pagerduty-critical

You can also view the full route tree:

amtool config routes show --config.file=/etc/alertmanager/alertmanager.yml

This prints a visual representation of your entire routing hierarchy, making it easy to spot misconfigurations.

Connect Alertmanager to Prometheus

If you haven’t already configured Prometheus to send alerts to Alertmanager, add the alerting block to your /etc/prometheus/prometheus.yml:

alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - localhost:9093

Reload Prometheus after the change:

sudo systemctl reload prometheus

Verify the connection by checking the Prometheus UI under Status > Runtime & Build Information. The Alertmanager should appear as a discovered endpoint.

How to Test Alert Notifications

Don’t wait for a real incident to find out your alerts are broken. Use amtool to send a test alert through the entire pipeline:

amtool alert add test_alert severity=critical instance="test-server:9090" --annotation=summary="This is a test alert" --annotation=description="Testing the alerting pipeline"

This creates a real alert in Alertmanager that will be routed through your configured receivers. Check your email, Slack, or PagerDuty to confirm delivery. The test alert will auto-resolve after Alertmanager’s resolve_timeout (5 minutes with the default config).

You can also trigger test alerts from Prometheus by temporarily lowering the threshold on an existing rule. For example, change HighCPUUsage threshold from 85 to 1 temporarily – this will fire on any system with more than 1% CPU usage, which is essentially every running system. Remember to revert the change after testing.

High Availability with Alertmanager Clustering

For production environments where a single Alertmanager is a single point of failure, run multiple instances in a cluster. Alertmanager uses a gossip protocol to synchronize silences and notification state across instances. Each instance needs the --cluster.peer flag pointing to the other instances:

ExecStart=/usr/local/bin/alertmanager \
  --config.file=/etc/alertmanager/alertmanager.yml \
  --storage.path=/var/lib/alertmanager \
  --web.listen-address=0.0.0.0:9093 \
  --cluster.listen-address=0.0.0.0:9094 \
  --cluster.peer=10.0.1.11:9094 \
  --cluster.peer=10.0.1.12:9094

In the Prometheus configuration, list all Alertmanager instances so that if one goes down, alerts still reach the others:

alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - 10.0.1.10:9093
            - 10.0.1.11:9093
            - 10.0.1.12:9093

The cluster automatically deduplicates notifications, so you won’t get triple alerts even though all three instances receive the same firing alert from Prometheus. Port 9094 needs to be open between the Alertmanager nodes for gossip communication.

Set Up Alerting in Grafana

Grafana can also use Alertmanager as a notification target. In Grafana, go to Alerting > Contact points and add a new contact point with type “Alertmanager”. Set the URL to http://localhost:9093. This lets Grafana-managed alerts use the same routing and grouping logic as Prometheus alerts.

Grafana alerting configuration showing Alertmanager as a contact point with connection status
Grafana alerting with Alertmanager as the notification backend

Troubleshooting Common Issues

Alertmanager fails with “no private IP found”

This is the most common issue on cloud VMs. Alertmanager’s cluster discovery tries to find a private RFC 1918 IP address for gossip communication. On cloud instances with only public interfaces or NAT-ed networking, it fails.

The fix for single-node deployments is to disable clustering entirely:

--cluster.listen-address=""

Add this to your systemd service ExecStart line. For multi-node clusters, explicitly set the bind address:

--cluster.listen-address="10.0.1.10:9094"

Emails not being delivered

Check the Alertmanager logs for SMTP errors:

sudo journalctl -u alertmanager -n 50 --no-pager | grep -i smtp

Common issues: wrong port (use 587 for TLS, 465 for SSL), authentication failure (Gmail needs App Password, not regular password), or firewall blocking outbound SMTP. Test SMTP connectivity from the server:

curl -v telnet://smtp.gmail.com:587

Alerts are firing but no notifications arrive

First verify alerts are reaching Alertmanager:

amtool alert query

If alerts appear there but notifications aren’t sent, the issue is in the route configuration. Test your routing:

amtool config routes test --config.file=/etc/alertmanager/alertmanager.yml severity=warning alertname=HighCPUUsage

If the test shows the wrong receiver, adjust your route matchers. Also check if the alert might be inhibited by an active inhibition rule or silenced by an active silence.

Slack messages not formatting correctly

Slack notification text uses Go template syntax. Common mistakes include missing range blocks for iterating over alerts and incorrect template variable names. Test your templates with a simple config first, then add complexity. The Alertmanager logs will show template rendering errors if the syntax is wrong.

Alertmanager restarts lose silence state

Alertmanager stores silence data in its --storage.path directory. If you’re running Alertmanager with a temporary storage path or the directory gets wiped, silences will be lost on restart. Make sure /var/lib/alertmanager is persistent and backed up. The nflog (notification log) that tracks which notifications were already sent is also stored there – without it, Alertmanager may re-send alerts that were already delivered.

Alerts are grouping incorrectly

The group_by setting determines which alerts are bundled into a single notification. If you group by ['alertname'] only, all InstanceDown alerts for different servers will be sent as one message. If you want separate notifications per server, add 'instance' to the group_by list. Be careful not to group by too many labels – this can lead to one notification per alert, which defeats the purpose of grouping and causes alert flood during outages.

Frequently Asked Questions

Can I run Alertmanager on a different server than Prometheus?

Yes. Update the Prometheus alertmanagers target to point to the remote server IP and port. Make sure port 9093 is open in the firewall between the two servers. Running Alertmanager separately is common in larger deployments where you want the alerting system to survive even if the Prometheus server has issues.

How do I add a new notification channel without downtime?

Edit the Alertmanager config file and reload the service. Alertmanager supports hot reloading – either send SIGHUP to the process or POST to the /-/reload endpoint if --web.enable-lifecycle is enabled. Validate the config first with amtool check-config to avoid breaking a running instance.

Conclusion

Alertmanager is the critical link between Prometheus detection and human action. With the routing, inhibition, and multi-channel configuration covered here, you have a production-ready alerting pipeline that routes critical issues to PagerDuty, sends warnings to Slack, and uses email as the catch-all. Fine-tune the group_wait, group_interval, and repeat_interval values based on your team’s response patterns – the defaults here are a solid starting point that prevent alert fatigue without missing real incidents.

Related Articles

Elasticseach Install Elasticsearch 8.x and Kibana on RHEL 10 / Rocky Linux 10 / AlmaLinux 10 Monitoring How To Monitor Etcd Cluster with Prometheus and Grafana Docker Monitor Docker Containers with Prometheus and Grafana Monitoring Install and Configure Telegraf on Debian 13/12

Leave a Comment

Press ESC to close