Camunda is a leading open-source workflow and BPM (Business Process Management) engine used by enterprises to orchestrate complex business processes — loan origination, order fulfillment, compliance workflows, and more. When Camunda is healthy, processes run invisibly in the background. When it fails, it fails quietly: the job executor stops picking up tasks, incidents accumulate in the process instance view, and business users only notice when SLA breaches show up in reports hours later.
Vigilmon adds external monitoring to Camunda through its REST API, giving you HTTP endpoint monitoring for engine health and job executor status, and heartbeat monitoring for long-running process completion. This tutorial covers both so you catch Camunda degradation before it becomes a business incident.
Why Camunda Needs External Monitoring
Camunda exposes Cockpit (a web UI), Optimize, and a REST API — but none of these alert you proactively when the engine degrades. External monitoring with Vigilmon adds:
- Engine availability checks — probing the REST API every minute to confirm the engine is responding
- Job executor health detection — a stuck job executor means no async tasks are being processed
- Incident backlog monitoring — rising incident counts indicate failed service tasks or expression errors
- Process completion heartbeats — verify that your most critical business processes complete end-to-end
- Multi-region probing independent of your internal APM or metrics stack
The most insidious Camunda failure mode is a healthy-looking engine (UI loads, REST API responds) where the job executor has silently stopped due to a database connection pool exhaustion or a classloader issue. Active timers expire, async continuations queue up, and nothing gets processed.
Step 1: Probe the Camunda REST API
Camunda Platform 7 and 8 both expose a REST API. The health check approach differs between them.
Camunda Platform 7 (Embedded or Standalone)
Camunda 7 does not have a built-in /health endpoint, but the REST API root and engine list are reliable availability indicators:
# Check engine availability
curl -s http://localhost:8080/engine-rest/engine
# Expected: [{"name":"default"}]
For a more complete health check, write a proxy that also checks the job executor:
// camunda7-health.js
const express = require('express');
const axios = require('axios');
const app = express();
const CAMUNDA_URL = process.env.CAMUNDA_URL || 'http://localhost:8080/engine-rest';
const CAMUNDA_USER = process.env.CAMUNDA_USER || 'admin';
const CAMUNDA_PASS = process.env.CAMUNDA_PASS || 'admin';
const auth = { username: CAMUNDA_USER, password: CAMUNDA_PASS };
app.get('/health/camunda', async (req, res) => {
try {
// Check engine is responding
const engineRes = await axios.get(`${CAMUNDA_URL}/engine`, { auth, timeout: 5000 });
if (!engineRes.data || engineRes.data.length === 0) {
return res.status(503).json({ status: 'down', reason: 'no_engine_found' });
}
// Check incident count — rising incidents indicate job failures
const incidentRes = await axios.get(`${CAMUNDA_URL}/incident/count`, { auth, timeout: 5000 });
const incidentCount = incidentRes.data?.count || 0;
const incidentThreshold = parseInt(process.env.INCIDENT_THRESHOLD || '10');
// Check failed jobs
const failedJobRes = await axios.get(`${CAMUNDA_URL}/job/count?withException=true&noRetriesLeft=true`, {
auth, timeout: 5000
});
const failedJobCount = failedJobRes.data?.count || 0;
const failedJobThreshold = parseInt(process.env.FAILED_JOB_THRESHOLD || '5');
const degraded = incidentCount > incidentThreshold || failedJobCount > failedJobThreshold;
if (degraded) {
return res.status(503).json({
status: 'degraded',
reason: 'incident_or_job_threshold_exceeded',
incidentCount,
failedJobCount,
incidentThreshold,
failedJobThreshold,
});
}
return res.status(200).json({
status: 'ok',
engines: engineRes.data.map(e => e.name),
incidentCount,
failedJobCount,
});
} catch (err) {
return res.status(503).json({ status: 'down', error: err.message });
}
});
app.listen(3006);
Camunda Platform 8 (Zeebe / Camunda Cloud)
Camunda 8 uses Zeebe as its workflow engine and exposes an Actuator health endpoint:
# Zeebe gateway health (Spring Boot Actuator)
curl -s http://localhost:9600/actuator/health
# Operate health (Camunda 8 web UI)
curl -s http://localhost:8081/actuator/health
For Camunda 8, probe both Zeebe gateway and Operate directly. If they are not externally reachable, deploy a health proxy in your cluster:
# camunda8_health.py
from fastapi import FastAPI
from fastapi.responses import JSONResponse
import httpx, os
app = FastAPI()
ZEEBE_HEALTH = os.environ.get("ZEEBE_HEALTH_URL", "http://zeebe-gateway:9600/actuator/health")
OPERATE_HEALTH = os.environ.get("OPERATE_HEALTH_URL", "http://operate:8081/actuator/health")
@app.get("/health/camunda")
async def camunda_health():
async with httpx.AsyncClient(timeout=5.0) as client:
try:
zeebe = await client.get(ZEEBE_HEALTH)
operate = await client.get(OPERATE_HEALTH)
zeebe_status = zeebe.json().get("status")
operate_status = operate.json().get("status")
if zeebe_status == "UP" and operate_status == "UP":
return {"status": "ok", "zeebe": zeebe_status, "operate": operate_status}
return JSONResponse(status_code=503, content={
"status": "degraded",
"zeebe": zeebe_status,
"operate": operate_status,
})
except Exception as e:
return JSONResponse(status_code=503, content={"status": "down", "error": str(e)})
Step 2: Monitor the Job Executor
The Camunda 7 job executor is responsible for picking up asynchronous continuations, timers, and message correlation. It runs on a thread pool and can silently stall without crashing the engine.
Add a dedicated job executor health check that looks at suspended jobs and queue depth:
// job-executor-health endpoint added to camunda7-health.js
app.get('/health/camunda/job-executor', async (req, res) => {
try {
// Jobs due for processing but not yet acquired
const dueJobsRes = await axios.get(
`${CAMUNDA_URL}/job/count?executable=true`,
{ auth, timeout: 5000 }
);
const dueJobs = dueJobsRes.data?.count || 0;
// Jobs that have been locked (acquired by executor) for a long time
// A large locked count with few completions suggests executor is stuck
const lockedJobsRes = await axios.get(
`${CAMUNDA_URL}/job/count?active=true`,
{ auth, timeout: 5000 }
);
const lockedJobs = lockedJobsRes.data?.count || 0;
const dueThreshold = parseInt(process.env.DUE_JOBS_THRESHOLD || '50');
if (dueJobs > dueThreshold) {
return res.status(503).json({
status: 'degraded',
reason: 'due_job_queue_backing_up',
dueJobs,
lockedJobs,
threshold: dueThreshold,
});
}
return res.status(200).json({ status: 'ok', dueJobs, lockedJobs });
} catch (err) {
return res.status(503).json({ status: 'down', error: err.message });
}
});
Step 3: Configure Vigilmon HTTP Monitors
- Log in to vigilmon.online and go to Monitors → New Monitor
- Choose HTTP / HTTPS
- Set the URL to your Camunda health endpoint:
https://your-app.example.com/health/camunda - Set the check interval to 1 minute
- Under Expected response, configure:
- Status code:
200 - Response body contains:
"status":"ok" - Response time threshold:
8000ms
- Status code:
- Under Alert channels, assign your Slack or PagerDuty channel
- Save the monitor
Add a second monitor for job executor health:
- URL:
https://your-app.example.com/health/camunda/job-executor - Expected:
200, body contains"status":"ok" - Interval:
2 minutes - Alert channel: workflow ops Slack channel
For Camunda 8, also monitor Operate directly if it is externally accessible:
- URL:
https://operate.example.com/actuator/health - Expected:
200, body contains:"status":"UP" - Interval:
1 minute
Step 4: Heartbeat Monitoring for Business Process Completion
Engine health and job executor checks tell you the infrastructure is operational. But the real question for mission-critical workflows is: are processes actually completing end-to-end?
Vigilmon heartbeat monitors verify process outcomes: a process completion listener sends a ping to Vigilmon each time a high-value process instance finishes. If completions stop, Vigilmon fires an alert — even if the engine appears healthy.
Set Up the Heartbeat Monitor
- In Vigilmon, go to Monitors → New Monitor → Heartbeat
- Set the name:
camunda-loan-origination-completions - Set the expected interval: 30 minutes (adjust to your process throughput)
- Set the grace period: 60 minutes
- Save — copy the heartbeat URL:
https://vigilmon.online/heartbeat/abc123xyz
Wire Heartbeat Into Camunda 7 Process
Add an execution listener on the End Event of your process:
// ProcessCompletionListener.java
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.ExecutionListener;
import java.net.HttpURLConnection;
import java.net.URL;
public class ProcessCompletionListener implements ExecutionListener {
private static final String HEARTBEAT_URL = System.getenv("VIGILMON_HEARTBEAT_URL");
@Override
public void notify(DelegateExecution execution) {
if (HEARTBEAT_URL == null || HEARTBEAT_URL.isBlank()) return;
try {
HttpURLConnection conn = (HttpURLConnection) new URL(HEARTBEAT_URL).openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(3000);
conn.setReadTimeout(3000);
conn.getResponseCode();
conn.disconnect();
} catch (Exception ignored) {
// Never let heartbeat failure interrupt process completion
}
}
}
Register the listener in your BPMN XML:
<endEvent id="EndEvent_LoanComplete">
<extensionElements>
<camunda:executionListener
class="com.example.ProcessCompletionListener"
event="end" />
</extensionElements>
</endEvent>
Camunda 8 (Zeebe) Job Worker
For Camunda 8, implement a job worker that handles a service task at the end of your process:
// CompletionJobWorker.java
import io.camunda.zeebe.client.ZeebeClient;
import io.camunda.zeebe.client.api.response.ActivatedJob;
import io.camunda.zeebe.client.api.worker.JobClient;
import io.camunda.zeebe.client.api.worker.JobHandler;
import java.net.HttpURLConnection;
import java.net.URL;
public class CompletionJobWorker implements JobHandler {
private final String heartbeatUrl = System.getenv("VIGILMON_HEARTBEAT_URL");
@Override
public void handle(JobClient client, ActivatedJob job) throws Exception {
// Your completion logic here
recordProcessCompletion(job.getVariablesAsMap());
// Ping Vigilmon
pingVigilmon();
client.newCompleteCommand(job.getKey()).send().join();
}
private void pingVigilmon() {
try {
HttpURLConnection conn = (HttpURLConnection) new URL(heartbeatUrl).openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(3000);
conn.setReadTimeout(3000);
conn.getResponseCode();
conn.disconnect();
} catch (Exception ignored) {}
}
}
Step 5: Alert Routing for Camunda Failures
| Monitor | Alert Channel | Priority |
|---|---|---|
| /health/camunda (engine down or incident spike) | Slack + PagerDuty | P1 |
| /health/camunda/job-executor (queue backlog) | Slack | P2 |
| Heartbeat: loan-origination-completions | Slack + email | P1 |
| Heartbeat: compliance-workflow-completions | Slack + PagerDuty | P1 |
Business process heartbeats should often be P1 even when engine checks are P2, because a process that stops completing means SLA breaches and unhappy customers — even if the engine itself is technically running.
Set a tighter grace period for critical processes (15 minutes) and a looser one for batch processes (4 hours). Configure a separate P3 heartbeat for low-priority batch workflows so alert fatigue from scheduled jobs doesn't mask critical failures.
Summary
Camunda failures are quiet by design — the engine keeps running while processes stall. External monitoring with Vigilmon gives you the observability your BPM platform was missing:
| Monitor Type | What It Covers |
|---|---|
| HTTP monitor on /health/camunda | Engine availability, incident backlog, failed job count |
| HTTP monitor on /health/camunda/job-executor | Job executor queue depth, stuck executors |
| Heartbeat monitor | End-to-end process completion, business outcome proof |
Get started free at vigilmon.online — your first Camunda monitor is live in under two minutes.