Using PHP and Cron to Automate Kannel Status Monitoring
Introduction
Kannel, a robust open-source SMS and WAP gateway, to control SMS traffic. Performance monitoring is essential to guarantee dependable message delivery and spot bottlenecks. This blog post will show you how to create a PHP script (kannel_monitor.php) that retrieves Kannel status information from a public endpoint (such as https://example.com/?kannel), saves it in a MySQL database, and uses a cron job to run every five minutes. We’ll also handle common production issues, such as the dreaded “Invalid response: Kannel status not found” error, and set up logging to /var/log/kannel_monitor.log for debugging.
Why Monitor Kannel?
Kannel’s status page provides real-time metrics, such as:
- SMSC Connections: Details about each SMS center (SMSC), including protocol, IP, ports, and status.
- SMS Metrics: Received, sent, queued, and failed messages, with rates (messages per second).
- DLR Metrics: Delivery report (DLR) statistics for tracking message delivery.
Our script will fetch this data, parse it, store it in a database, and log operations for troubleshooting.
Step 1: Designing the Script
The kannel_monitor.php script will:
- Fetch Kannel status data using file_get_contents.
- Parse metrics (SMSC connections, SMS, and DLR) using regular expressions.
- Store the data in a MySQL database.
- Log operations to /var/log/kannel_monitor.log.
- Run every 5 minutes via a cron job.
<?php
// Fetch status text
$context = stream_context_create([
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true
]
]);
$statusText = @file_get_contents($kannelUrl, false, $context);
if ($statusText === false) {
$error = error_get_last();
error_log("Failed to fetch status from $kannelUrl: " . ($error['message'] ?? 'Unknown error'), 3, $logFile);
exit(1);
}
// Debug: Save response to file
file_put_contents(__DIR__ . '/debug_kannel_status.html', $statusText);
// Validate response
if (strpos($statusText, 'Kannel bearerbox version') === false && strpos($statusText, 'SMSC:') === false) {
error_log("Invalid response: Kannel status not found. Check URL or response format.", 3, $logFile);
exit(1);
}
// Initialize metrics
$smsReceivedTotal = 0;
$smsSentTotal = 0;
$smsQueuedReceived = 0;
$smsQueuedSent = 0;
$smsInboundRateAvg = 0.0;
$smsOutboundRateAvg = 0.0;
$dlrReceivedTotal = 0;
$dlrSentTotal = 0;
$dlrQueued = 0;
$dlrInboundRateAvg = 0.0;
$smscRecords = [];
// Parse SMSC connections
preg_match_all(
'/(\w+\[\w+\])\s+(SMPP|HTTP|FAKE):([^:]+):(\d+)\/(\d+):([^:]*):?(SMPP|XCV)?\s+\((\w+)[^,]+,\s*rcvd: sms (\d+).*?\(([\d.]+),([\d.]+),([\d.]+)\).*?sent: sms (\d+).*?\(([\d.]+),([\d.]+),([\d.]+)\).*?dlr (\d+).*?\(([\d.]+),([\d.]+),([\d.]+)\).*?failed (\d+), queued (\d+)/',
$statusText, $smscMatches, PREG_SET_ORDER
);
foreach ($smscMatches as $match) {
$smscRecords[] = [
'provider_name' => $match[1],
'protocol' => $match[2],
'ip_address' => $match[3] == 'generic' ? null : $match[3],'
'smsc_status' => $match[8],
'provider_sms_received' => (int) $match[9],
'provider_dlr_received_rate_avg' => (float) $match[19],
'provider_sms_failed' => (int) $match[21],
'provider_sms_queued' => (int) $match[22]
];
}
// Parse aggregate metrics
if (preg_match('/SMS: received (\d+) \((\d+) queued\), sent (\d+) \((\d+) queued\)/', $statusText, $matches)) {
$smsReceivedTotal = (int) $matches[1];
$smsQueuedReceived = (int) $matches[2];
$smsSentTotal = (int) $matches[3];
$smsQueuedSent = (int) $matches[4];
}
if (preg_match('/SMS: inbound \(([\d.]+),([\d.]+),([\d.]+)\) msg\/sec, outbound \(([\d.]+),([\d.]+),([\d.]+)\) msg\/sec/', $statusText, $matches)) {
$smsInboundRateAvg = (float) $matches[2];
$smsOutboundRateAvg = (float) $matches[5];
}
if (preg_match('/DLR: received (\d+), sent (\d+)/', $statusText, $matches)) {
$dlrReceivedTotal = (int) $matches[1];
$dlrSentTotal = (int) $matches[2];
}
if (preg_match('/DLR: (\d+) queued/', $statusText, $matches)) {
$dlrQueued = (int) $matches[1];
}
if (preg_match('/DLR: inbound \(([\d.]+),([\d.]+),([\d.]+)\) msg\/sec/', $statusText, $matches)) {
$dlrInboundRateAvg = (float) $matches[2];
}
// Connect to database
$db = new mysqli($dbHost, $dbUser, $dbPass, $dbName);
if ($db->connect_error) {
error_log("DB connection failed: " . $db->connect_error, 3, $logFile);
exit(1);
}
// Insert records
$stmt = $db->prepare("
INSERT INTO kannel_monitor (
timestamp, provider_name, smsc_status, protocol, ip_address, port_transmit, port_receive, system_id,
provider_sms_received, provider_sms_sent, provider_dlr_received, provider_sms_failed, provider_sms_queued,
provider_sms_received_rate_avg, provider_sms_sent_rate_avg, provider_dlr_received_rate_avg,
sms_received_total, sms_sent_total, sms_queued_received, sms_queued_sent,
sms_inbound_rate_avg, sms_outbound_rate_avg, dlr_received_total, dlr_sent_total, dlr_queued,
dlr_inbound_rate_avg
)
VALUES (NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
if (!$stmt) {
error_log("Prepare failed: " . $db->error, 3, $logFile);
$db->close();
exit(1);
}
foreach ($smscRecords as $record) {
$stmt->bind_param(
'ssssiiisiiiidddiiiiddiiid',
$record['provider_name'],
$record['smsc_status'],
$record['protocol'],
$record['ip_address'],
$record['port_transmit'],
$smsQueuedSent,
$smsInboundRateAvg,
$smsOutboundRateAvg,
$dlrReceivedTotal,
$dlrSentTotal,
$dlrQueued,
$dlrInboundRateAvg
);
if (!$stmt->execute()) {
error_log("Insert failed for {$record['provider_name']}: " . $db->error, 3, $logFile);
break;
} else {
error_log("Inserted record for {$record['provider_name']}", 3, $logFile);
}
}
// Insert aggregate metrics if no SMSC records
if (empty($smscRecords)) {
$nullStr = null;
$nullInt = null;
$nullFloat = null;
$stmt->bind_param(
'ssssiiisiiiidddiiiiddiiid',
$nullStr,
$nullStr,
$nullStr,
$nullInt,
$nullFloat,
$nullFloat,
$nullFloat,
$smsReceivedTotal,
$smsSentTotal,
$smsQueuedReceived,
$smsQueuedSent,
$smsInboundRateAvg,
$smsOutboundRateAvg,
$dlrReceivedTotal,
$dlrSentTotal,
$dlrQueued,
$dlrInboundRateAvg
);
if (!$stmt->execute()) {
error_log("Insert failed for aggregate metrics: " . $db->error, 3, $logFile);
} else {
error_log("Inserted aggregate metrics", 3, $logFile);
}
}
$stmt->close();
$db->close();
error_log("Data stored successfully", 3, $logFile)
Step 2: The Script
Complete kannel_monitor.php script, which I developed to monitor a Kannel instance at https://example.com/?kannel. It includes error handling, logging, and validation to avoid issues like the “Invalid response” error I encountered in production.
Step 3: Setting Up the Environment
To run this script in production, follow these steps:
- Save the Script: Save the script to
/var/www/html/scripts/kannel_monitor.php
:
Paste the script above and set permissions:chmod 644 /var/www/html/scripts/kannel_monitor.php chown www-data:www-data /var/www/html/scripts/kannel_monitor.php
- Create .env File:
KANNEL_URL=https://example.com/?
kannel DB_HOST=localhost
DB_USER=your_db_user
DB_PASS=your_db_password
DB_NAME=your_db_name
- Create the Log File: Set up
/var/log/kannel_monitor.log
:sudo touch /var/log/kannel_monitor.log sudo chmod 664 /var/log/kannel_monitor.log sudo chown www-data:www-data /var/log/kannel_monitor.log
- Set Up the Cron Job: Schedule the script to run every 5 minutes:
crontab -e
Add:*/5 * * * * /usr/bin/php /var/www/html/scripts/kannel_monitor.php >> /var/log/kannel_monitor.log 2>&1
Verify:crontab -l
Step 4: Testing the Script
Test the script manually:
/usr/bin/php /var/www/html/scripts/kannel_monitor.php
Check the log file:
cat /var/log/kannel_monitor.log
Expected output:
Inserted record for provider1[smsc1]
Inserted aggregate metrics
Data stored successfully
It should contain Kannel status data (e.g., SMSC:, SMS: received).
Verify database records:
SELECT * FROM kannel_monitor;
Wait 5 minutes to confirm the cron job runs, or temporarily set it to run every minute:
* * * * /usr/bin/php /var/www/html/scripts/kannel_monitor.php >> /var/log/kannel_monitor.log 2>&1

Conclusion
Using kannel_monitor.php with a cron job is an easy way to keep track of your SMS gateway. The script grabs, processes, and saves data reliably, with logs to help fix problems.