How to Monitor Your Self-Hosted OrangeHRM Instance (and Get Alerted Before HR Grinds to a Halt)
OrangeHRM downtime isn't just an inconvenience — it's a compliance risk. When your self-hosted HR system goes dark, leave requests pile up unanswered, payroll jobs miss their processing windows, and new employee onboarding grinds to a halt. By the time someone notices, you're dealing with data integrity questions on top of an outage.
This guide walks you through setting up complete monitoring for a self-hosted OrangeHRM deployment: HTTP availability checks, PHP-FPM process health, MySQL connectivity, and heartbeat monitoring for critical background workflows — all wired to instant alerts.
What breaks silently in OrangeHRM
OrangeHRM runs on PHP/Symfony behind a web server (typically Apache or Nginx), backed by MySQL and a cron-driven task scheduler. When things go wrong, they tend to go wrong quietly:
Web availability failures — the application returns 500 errors or a blank page. PHP-FPM crashes, Symfony caches go stale, or a misconfigured .htaccess block. Users see nothing useful; your monitoring should catch it first.
PHP-FPM pool exhaustion — under load, PHP-FPM worker pools fill up. New requests queue or get dropped. The application appears intermittently slow or broken with no obvious error.
MySQL connectivity issues — OrangeHRM's entire data layer sits on MySQL. Connection pool exhaustion, slow queries, or a replication lag on replica nodes causes transaction failures that look like application bugs.
Silent cron failures — leave carryover calculations, payroll batch jobs, and email notification dispatches all run via cron. If cron stops firing, those processes silently stop working. No exception is raised; nothing alerts you.
Step 1: Create a health check endpoint
OrangeHRM doesn't ship a /health endpoint, but you can add one that checks what actually matters: PHP responsiveness and database connectivity.
Create a standalone health check file outside OrangeHRM's Symfony routing layer for simplicity:
<?php
// /var/www/orangehrm/public/health.php
header('Content-Type: application/json');
$checks = [];
$status = 200;
// Database connectivity check
try {
$dsn = 'mysql:host=' . ($_ENV['ORANGEHRM_DB_HOST'] ?? 'localhost') .
';dbname=' . ($_ENV['ORANGEHRM_DATABASE_NAME'] ?? 'orangehrm');
$pdo = new PDO(
$dsn,
$_ENV['ORANGEHRM_DB_USERNAME'] ?? 'orangehrm',
$_ENV['ORANGEHRM_DB_PASSWORD'] ?? '',
[PDO::ATTR_TIMEOUT => 3]
);
$pdo->query('SELECT 1');
$checks['database'] = 'ok';
} catch (Exception $e) {
$checks['database'] = 'error';
$status = 500;
}
// Check that OrangeHRM's cache directory is writable
$cacheDir = __DIR__ . '/../cache';
$checks['cache_writable'] = is_writable($cacheDir) ? 'ok' : 'error';
if ($checks['cache_writable'] === 'error') {
$status = 500;
}
http_response_code($status);
echo json_encode([
'status' => $status === 200 ? 'healthy' : 'degraded',
'checks' => $checks,
'timestamp' => date('c'),
]);
Protect this file so it's only accessible from your monitoring service's IP:
# In your Nginx server block
location = /health.php {
allow 203.0.113.10; # Vigilmon probe IP range
deny all;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Test it:
curl -i http://localhost/health.php
# HTTP/1.1 200 OK
# {"status":"healthy","checks":{"database":"ok","cache_writable":"ok"},"timestamp":"2026-01-01T00:00:00+00:00"}
Step 2: Set up HTTP uptime monitoring in Vigilmon
With the health endpoint live, point Vigilmon at it:
- Sign up at vigilmon.online
- Click New Monitor → HTTP
- Enter
https://hrm.yourdomain.com/health.php - Set check interval to 5 minutes
- Add an assertion: response body contains
"status":"healthy" - Save
Add separate monitors for the core application URLs:
| Monitor name | URL | Why |
|---|---|---|
| OrangeHRM Login | https://hrm.yourdomain.com/auth/login | Auth layer is reachable |
| OrangeHRM Dashboard | https://hrm.yourdomain.com/dashboard | Main app is rendering |
| OrangeHRM API | https://hrm.yourdomain.com/api/v2/leave/leave-types | REST API is responding |
Each monitor runs independently. An API failure with a healthy homepage tells you exactly where the break is.
Step 3: Monitor PHP-FPM pool health
PHP-FPM has a built-in status endpoint that exposes pool metrics. Enable it:
; /etc/php/8.1/fpm/pool.d/www.conf
pm.status_path = /fpm-status
Expose it via Nginx (internal access only):
location = /fpm-status {
allow 127.0.0.1;
allow 203.0.113.10; # Vigilmon probe
deny all;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Reload PHP-FPM:
systemctl reload php8.1-fpm
curl http://localhost/fpm-status
# pool: www
# process manager: dynamic
# start time: ...
# active processes: 2
# idle processes: 3
# max active processes: 8
Create an HTTP monitor in Vigilmon for http://hrm.yourdomain.com/fpm-status checking that the response is 200. If PHP-FPM crashes, this endpoint stops responding and Vigilmon alerts you immediately.
Step 4: Heartbeat monitoring for critical HR workflows
HTTP monitoring catches server failures. But payroll batch jobs, leave carryover calculations, and email notifications all run on cron — and cron failures are invisible without heartbeats.
The heartbeat pattern: your cron job pings a URL at the end of every successful run. If Vigilmon stops seeing pings within the expected window, it alerts you.
Leave management workflow heartbeat
#!/bin/bash
# /opt/orangehrm-jobs/process-leave.sh
cd /var/www/orangehrm
# Run OrangeHRM's leave processing
php bin/console orangehrm:leave:generate-emp-leave-request --env=prod
if [ $? -eq 0 ]; then
curl -fsS --retry 3 "${VIGILMON_LEAVE_HEARTBEAT_URL}" > /dev/null 2>&1
echo "[$(date)] Leave processing completed and heartbeat sent"
else
echo "[$(date)] Leave processing FAILED — heartbeat NOT sent" >&2
fi
Payroll processing heartbeat
#!/bin/bash
# /opt/orangehrm-jobs/process-payroll.sh
cd /var/www/orangehrm
php bin/console orangehrm:payroll:process --env=prod
if [ $? -eq 0 ]; then
curl -fsS --retry 3 "${VIGILMON_PAYROLL_HEARTBEAT_URL}" > /dev/null 2>&1
fi
Email notification delivery heartbeat
<?php
// /opt/orangehrm-jobs/send-notifications.php
// Run via cron to dispatch queued email notifications
require_once '/var/www/orangehrm/vendor/autoload.php';
// Process queued notifications...
$dispatcher = new OrangeHRM\Core\Notification\NotificationDispatcher();
$sent = $dispatcher->dispatchPending();
if ($sent !== false) {
$heartbeatUrl = getenv('VIGILMON_EMAIL_HEARTBEAT_URL');
if ($heartbeatUrl) {
$ch = curl_init($heartbeatUrl);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);
}
}
Crontab configuration
# /etc/cron.d/orangehrm-monitors
# Leave processing — daily at 1 AM
0 1 * * * orangehrm /opt/orangehrm-jobs/process-leave.sh >> /var/log/orangehrm/leave.log 2>&1
# Payroll processing — 1st of each month at 2 AM
0 2 1 * * orangehrm /opt/orangehrm-jobs/process-payroll.sh >> /var/log/orangehrm/payroll.log 2>&1
# Email notifications — every 15 minutes
*/15 * * * * orangehrm php /opt/orangehrm-jobs/send-notifications.php >> /var/log/orangehrm/notifications.log 2>&1
# Report generation — every hour
0 * * * * orangehrm php /var/www/orangehrm/bin/console orangehrm:report:generate --env=prod && curl -fsS "$VIGILMON_REPORTS_HEARTBEAT_URL" > /dev/null
Set environment variables in /etc/environment or in the cron environment:
VIGILMON_LEAVE_HEARTBEAT_URL=https://vigilmon.online/heartbeats/your-leave-token
VIGILMON_PAYROLL_HEARTBEAT_URL=https://vigilmon.online/heartbeats/your-payroll-token
VIGILMON_EMAIL_HEARTBEAT_URL=https://vigilmon.online/heartbeats/your-email-token
VIGILMON_REPORTS_HEARTBEAT_URL=https://vigilmon.online/heartbeats/your-reports-token
In Vigilmon, create a Heartbeat Monitor for each job:
- Click New Monitor → Heartbeat
- Name it (e.g. "OrangeHRM Payroll Job")
- Set the expected interval (match your cron schedule — payroll monthly means 32 days to be safe)
- Copy the ping URL and set it in your environment
- Save
Step 5: Employee onboarding pipeline monitoring
The onboarding pipeline in OrangeHRM involves multiple sequential steps — creating the employee record, setting up access, triggering welcome emails, and assigning to departments. Create a lightweight check that validates the pipeline is functional:
<?php
// /var/www/orangehrm/public/onboarding-health.php
header('Content-Type: application/json');
try {
$dsn = 'mysql:host=localhost;dbname=orangehrm';
$pdo = new PDO($dsn, $_ENV['ORANGEHRM_DB_USERNAME'], $_ENV['ORANGEHRM_DB_PASSWORD']);
// Check for any stuck onboarding tasks (pending > 24 hours)
$stmt = $pdo->query("
SELECT COUNT(*) as stuck_count
FROM ohrm_employee_work_shift
WHERE created_at < DATE_SUB(NOW(), INTERVAL 24 HOUR)
AND status = 'pending'
");
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$status = ($row['stuck_count'] == 0) ? 'ok' : 'degraded';
http_response_code($status === 'ok' ? 200 : 500);
echo json_encode(['onboarding_pipeline' => $status, 'stuck_tasks' => $row['stuck_count']]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['onboarding_pipeline' => 'error']);
}
Add this as an HTTP monitor in Vigilmon checking the response body for "onboarding_pipeline":"ok".
Step 6: Alerts via Slack or email
Configure notifications in Vigilmon so alerts reach whoever is on call:
Slack:
- Create an incoming webhook in your Slack workspace
- Go to Notifications → New Channel → Slack in Vigilmon
- Paste the webhook URL and test it
- Enable it on all OrangeHRM monitors
Email:
- Go to Notifications → New Channel → Email
- Add your HR team email or a dedicated ops distribution list
- Enable on critical monitors (web availability, database, payroll heartbeat)
A payroll heartbeat miss alert looks like:
🔴 MISSED: OrangeHRM Payroll Job
Expected every: 32 days
Last successful ping: 34 days ago
Monitor: OrangeHRM Payroll Job
Step 7: Status page for your HR team
A shared status page lets your HR team check system health without filing an IT ticket every time something feels slow:
- Go to Status Pages → New Status Page
- Name it "HR Systems Status"
- Add your OrangeHRM monitors
- Share the URL with your HR team and link it from your intranet
What you've built
| Component | Monitoring approach |
|---|---|
| Web application availability | HTTP monitor on /health.php |
| PHP-FPM pool health | HTTP monitor on /fpm-status |
| MySQL connectivity | Database check inside health endpoint |
| Leave management workflow | Heartbeat — daily cron job |
| Payroll processing | Heartbeat — monthly batch job |
| Email notification delivery | Heartbeat — every 15 minutes |
| Report generation | Heartbeat — hourly job |
| Onboarding pipeline | HTTP monitor on /onboarding-health.php |
The full setup runs on Vigilmon's free tier and takes less time than diagnosing a payroll job that silently failed a week ago.
Next steps
- Add a monitor for your OrangeHRM backup job — data loss during an incident is worse than the incident itself
- Set up response time tracking to catch gradual slowdowns before they become outages
- Monitor your MySQL slow query log volume as a leading indicator of database stress
Get started free at vigilmon.online.