AWS SQS queues are deceptively quiet. Messages pile up, consumers stall, and dead-letter queues fill silently — all while your application looks healthy from the outside. CloudWatch can graph queue depth, but it lives inside your AWS account and can be misconfigured by the same team that caused the problem. Vigilmon adds an independent external layer: HTTP health checks on your consumer services, heartbeat monitors for scheduled workers, and webhook alerts when things go wrong.
This tutorial shows you how to build meaningful monitoring for SQS-based architectures using Vigilmon.
What You'll Build
- A health endpoint on your SQS consumer service that checks real queue connectivity
- A Vigilmon HTTP monitor with appropriate timeout settings for queue consumers
- A heartbeat pattern for batch/scheduled SQS workers
- Dead-letter queue (DLQ) awareness via a lightweight Lambda probe
- An alerting strategy for queue depth spikes and consumer failures
Prerequisites
- AWS account with at least one SQS queue and a consumer service (any runtime)
- AWS CLI or CDK/SAM configured locally
- A free account at vigilmon.online
Step 1: Add a Health Endpoint to Your Consumer Service
Your SQS consumer is typically a long-running service (ECS task, EC2 instance, Lambda with event source mapping). Add a /health endpoint that probes real SQS connectivity.
Node.js (Express)
// routes/health.js
import { SQSClient, GetQueueAttributesCommand } from "@aws-sdk/client-sqs";
const sqs = new SQSClient({});
export async function healthHandler(req, res) {
const checks = {};
let ok = true;
// Verify we can read the queue
try {
const result = await sqs.send(
new GetQueueAttributesCommand({
QueueUrl: process.env.QUEUE_URL,
AttributeNames: ["ApproximateNumberOfMessages", "ApproximateNumberOfMessagesNotVisible"],
})
);
const depth = parseInt(result.Attributes.ApproximateNumberOfMessages, 10);
const inFlight = parseInt(result.Attributes.ApproximateNumberOfMessagesNotVisible, 10);
checks.sqs = "ok";
checks.queueDepth = depth;
checks.inFlight = inFlight;
// Alert-level check: flag if queue is unusually deep
if (depth > parseInt(process.env.QUEUE_DEPTH_WARN || "1000", 10)) {
checks.sqs = "backlogged";
ok = false;
}
} catch (err) {
checks.sqs = `error: ${err.message}`;
ok = false;
}
res.status(ok ? 200 : 503).json({
status: ok ? "ok" : "degraded",
service: "sqs-consumer",
checks,
});
}
Python (FastAPI)
# routers/health.py
import os
import boto3
from fastapi import APIRouter
from botocore.exceptions import ClientError
router = APIRouter()
sqs = boto3.client("sqs")
@router.get("/health")
async def health():
checks = {}
ok = True
try:
resp = sqs.get_queue_attributes(
QueueUrl=os.environ["QUEUE_URL"],
AttributeNames=["ApproximateNumberOfMessages"],
)
depth = int(resp["Attributes"]["ApproximateNumberOfMessages"])
checks["sqs"] = "ok"
checks["queueDepth"] = depth
warn_threshold = int(os.environ.get("QUEUE_DEPTH_WARN", "1000"))
if depth > warn_threshold:
checks["sqs"] = "backlogged"
ok = False
except ClientError as e:
checks["sqs"] = f"error: {e.response['Error']['Code']}"
ok = False
return {
"status": "ok" if ok else "degraded",
"service": "sqs-consumer",
"checks": checks,
}
Go (net/http)
// internal/health/handler.go
package health
import (
"context"
"encoding/json"
"net/http"
"os"
"strconv"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sqs"
)
func Handler(client *sqs.Client) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
checks := map[string]any{}
ok := true
out, err := client.GetQueueAttributes(context.Background(), &sqs.GetQueueAttributesInput{
QueueUrl: aws.String(os.Getenv("QUEUE_URL")),
AttributeNames: []types.QueueAttributeName{"ApproximateNumberOfMessages"},
})
if err != nil {
checks["sqs"] = "error: " + err.Error()
ok = false
} else {
depth, _ := strconv.Atoi(out.Attributes["ApproximateNumberOfMessages"])
checks["sqs"] = "ok"
checks["queueDepth"] = depth
}
w.Header().Set("Content-Type", "application/json")
if !ok {
w.WriteHeader(http.StatusServiceUnavailable)
}
json.NewEncoder(w).Encode(map[string]any{"status": map[bool]string{true: "ok", false: "degraded"}[ok], "checks": checks})
}
}
Step 2: Configure the Vigilmon HTTP Monitor
- Log in to Vigilmon → Add Monitor → HTTP.
- URL: your consumer service's health endpoint (e.g.
https://consumer.example.com/health). - Check interval: 60 seconds.
- Response timeout: 10 seconds.
- Expected status:
200. - JSON assertion: path
status, expected valueok.
Tip: If your consumer runs inside a VPC with no public endpoint, expose the health route through an internal ALB or API Gateway VPC link, or use a lightweight reverse proxy (nginx, Caddy) that makes the health port accessible externally.
Step 3: Heartbeat Monitoring for SQS Workers
Scheduled or batch workers that drain SQS queues are invisible to HTTP monitors. Use Vigilmon's Heartbeat monitor and have the worker ping it after each successful batch.
Worker heartbeat (Node.js)
// worker/index.mjs
import { SQSClient, ReceiveMessageCommand, DeleteMessageCommand } from "@aws-sdk/client-sqs";
const sqs = new SQSClient({});
async function processBatch() {
const { Messages } = await sqs.send(
new ReceiveMessageCommand({
QueueUrl: process.env.QUEUE_URL,
MaxNumberOfMessages: 10,
WaitTimeSeconds: 20,
})
);
if (!Messages?.length) return;
for (const msg of Messages) {
await processMessage(msg);
await sqs.send(
new DeleteMessageCommand({
QueueUrl: process.env.QUEUE_URL,
ReceiptHandle: msg.ReceiptHandle,
})
);
}
// Ping Vigilmon: heartbeat only fires when work succeeded
await fetch(process.env.VIGILMON_HEARTBEAT_URL, { method: "POST" });
}
// Long-poll loop
while (true) {
await processBatch().catch(console.error);
}
In Vigilmon, create a Heartbeat monitor:
- Set the grace period to 2× your expected poll interval (e.g., 4 minutes for a 2-minute loop).
- If the heartbeat stops arriving, Vigilmon fires an alert — meaning your worker crashed or the queue drained unexpectedly.
Step 4: Dead-Letter Queue Probe with Lambda
When messages land in a DLQ, your application has a silent failure. Run a lightweight Lambda on a schedule to check DLQ depth and ping a Vigilmon heartbeat only when the DLQ is empty.
// dlq-probe/index.mjs
import { SQSClient, GetQueueAttributesCommand } from "@aws-sdk/client-sqs";
const sqs = new SQSClient({});
export async function handler() {
const result = await sqs.send(
new GetQueueAttributesCommand({
QueueUrl: process.env.DLQ_URL,
AttributeNames: ["ApproximateNumberOfMessages"],
})
);
const dlqDepth = parseInt(result.Attributes.ApproximateNumberOfMessages, 10);
if (dlqDepth === 0) {
// DLQ is healthy — send heartbeat
await fetch(process.env.VIGILMON_HEARTBEAT_URL, { method: "POST" });
console.log("DLQ is empty — heartbeat sent");
} else {
// Do NOT ping — Vigilmon will fire a missed-heartbeat alert
console.warn(`DLQ has ${dlqDepth} messages — withholding heartbeat`);
}
}
Schedule this Lambda every 5 minutes with EventBridge. In Vigilmon, set the heartbeat grace period to 10 minutes. Any messages in the DLQ for more than 10 minutes will trigger an alert.
Step 5: Alerting Strategy
| Alert channel | When to use | |---|---| | Email / PagerDuty | Consumer service returns 503 (queue unreachable) | | Slack webhook | Heartbeat missed (worker stalled) | | Slack + Email | DLQ heartbeat missed (messages failing processing) | | Status page | Public-facing services backed by SQS |
Configure alert escalation in Vigilmon: notify immediately on first failure, re-notify after 5 minutes if still down. This catches real outages while tolerating transient SQS API blips.
What Vigilmon Catches That CloudWatch Misses
| Scenario | CloudWatch | Vigilmon |
|---|---|---|
| Consumer service crashes (HTTP 503) | Needs explicit ALB target group alarm | HTTP monitor catches immediately |
| Worker silently stops processing | Needs metric math on NumberOfMessagesSent | Heartbeat grace period fires alert |
| DLQ accumulates failed messages | Needs DLQ depth alarm + SNS | DLQ probe + heartbeat monitor |
| AWS Console or IAM access breaks | Your alarms may also fail | Vigilmon is external — unaffected |
| Cross-account queue access issue | Needs account-level alarm | HTTP monitor catches at the consumer |
SQS makes async architectures resilient, but resilience is invisible without monitoring. Consumer crashes, worker stalls, and DLQ backlogs all look like silence. External health checks from Vigilmon give you the independent signal you need — before your customers notice.
Start monitoring your SQS consumers in under 5 minutes — register free at vigilmon.online.