Design on-call rotations that are sustainable, effective, and respectful of engineers' time. Covers rotation design, escalation policies, runbook standards, compensation models, and preventing on-call burnout.
On-call engineering is the discipline of ensuring systems remain stable, observable, and recoverable when engineers are not at their desks. It matters when a service fails at 2 a.m., when a database replica falls out of sync during a deployment, or when a third-party API begins returning 500s across a region. It is the practice of structuring responsibilities, tooling, and workflows so that anyone on-call—regardless of depth of domain knowledge—can triage, diagnose, and resolve incidents efficiently.
When an alert fires, the on-call engineer must act quickly. The goal is to reduce mean time to acknowledge (MTTA) and mean time to resolve (MTTR) through a well-structured, repeatable process.
curl -X POST \
-H "Authorization: Bearer $ONCALL_TOKEN" \
-H "Content-Type: application/json" \
-d '{"incident_id": "INC-2023-087", "acknowledged_by": "sarah", "notes": "Initial triage: system is unresponsive."}' \
https://alerting.internal/api/v1/incidents/acknowledge
# Retrieve logs from the past 10 minutes using structured JSON logs
kubectl logs deployment/api-gateway -n prod --since=10m --tail=100 | jq '.timestamp, .level, .message, .error, .trace_id'
trace_id from a failed request in the logs. trace_id to visualize the request flow. systemctl restart api-gateway
kubectl rollout undo deployment/api-gateway -n prod --to-revision=12
kubectl scale deployment/api-gateway -n prod --replicas=8
# /etc/config/api-gateway.yaml
connection_pool:
max_connections: 150
idle_timeout: 300s
max_lifetime: 1h
Apply with systemctl reload api-gateway.
curl -v https://api.example.com/v1/users/123
An effective on-call rotation ensures consistent coverage and reduces burnout. The system must define who is on-call, when, and how they are notified.
{
"name": "API Team",
"frequency": "weekly",
"start_time": "2023-10-01T00:00:00Z",
"rotations": [
{
"start": "2023-10-01T00:00:00Z",
"end": "2023-10-08T00:00:00Z",
"oncall": "alice",
"backup": "bob"
},
{
"start": "2023-10-08T00:00:00Z",
"end": "2023-10-15T00:00:00Z",
"oncall": "bob",
"backup": "charlie"
}
]
}
# Update on-call roster using a script triggered by CI/CD
curl -X POST \
-H "Content-Type: application/json" \
-d '{"team": "api", "current_oncall": "alice", "start": "2023-10-01T00:00:00Z"}' \
https://oncall.internal/api/v1/rotation
Alerts must be actionable, timely, and avoid noise.
error_rate > 0.05 over 5 minutes latency_p95 > 1s over 10 minutes service_down (e.g., HTTP 5xx for 2 consecutive minutes) alert: "High Error Rate in API Gateway"
description: "API Gateway returns >5% errors over 5 minutes."
severity: "critical"
query: |
rate(errors{service="api-gateway"}[5m]) / rate(requests{service="api-gateway"}[5m]) > 0.05
for: "5m"
labels:
team: api
service: api-gateway
annotations:
summary: "High error rate on {{ $labels.service }}"
description: |
API Gateway error rate has exceeded 5% over the last 5 minutes.
Current value: {{ $value }}
More details: https://monitoring.internal/incidents/{{ $labels.incident_id }}
Every incident is a learning opportunity. The documentation must capture not only what happened but why.
# Postmortem: High Latency on Checkout Service (2023-10-04)
## Timeline
- 02:15:42: Error rate spikes to 12%
- 02:18:10: First alert fired
- 02:20:03: On-call engineer acknowledges incident
- 02:23:45: Logs show slow DB queries in `checkout` service
- 02:31:00: DB index created; latency drops
- 02:45:00: Incident resolved
## Root Cause
- Missing index on `orders` table for `user_id` and `status`
- Query: `SELECT * FROM orders WHERE user_id = ? AND status = 'pending'` took 800ms
- Index created during previous deployment but not applied to production
## Actions Taken
- Added index on `orders(user_id, status)`
- Verified query performance with `EXPLAIN ANALYZE`
- Updated deployment script to include index creation
## Lessons Learned
- All database migrations should include a migration status check.
- Add alert for "missing index" on high-load tables.
- Include DB schema changes in deployment checklist.
The on-call engineer is responsible for more than just responding to alerts.
[INC-2023-087] High Error Rate in API Gateway
Acknowledged by sarah (on-call)
Status: In progress
Summary: Error rate > 5% over 5 minutes.
Next steps:
- Investigate database queries
- Check load balancer health
- Review recent deployments
config.yaml because it’s in the wrong directory. [Unit]
Description=API Gateway
[Service]
ExecStart=/usr/bin/api-gateway --config /etc/config/api-gateway.yaml
WorkingDirectory=/opt/api-gateway
Restart=always
On-call engineering relies on a few core tools and data flows.
CREATE TABLE alerts (
id SERIAL PRIMARY KEY,
incident_id VARCHAR(64) UNIQUE,
title TEXT,
description TEXT,
severity TEXT,
status TEXT, -- pending, acknowledged, in_progress, resolved
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
acknowledged_by TEXT,
resolved_by TEXT
);
CREATE TABLE incidents (
id SERIAL PRIMARY KEY,
incident_id VARCHAR(64) UNIQUE,
title TEXT,
description TEXT,
status TEXT,
created_at TIMESTAMPTZ,
acknowledged_at TIMESTAMPTZ,
resolved_at TIMESTAMPTZ,
root_cause TEXT,
solution TEXT,
follow_ups JSONB,
logs JSONB,
traces JSONB
);
main, generate next rotation and deploy to on-call service On-call engineering is not a role—it is a process, a culture, and a system. It thrives when every engineer knows how to respond to an alert, every incident is documented, and the handover between shifts is seamless. It is not enough to have alerts; the system must be designed so that anyone can become an on-call engineer after a single shift.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy