{"id":8975,"date":"2025-05-24T13:23:38","date_gmt":"2025-05-24T07:53:38","guid":{"rendered":"https:\/\/pheonixsolutions.com\/blog\/?p=8975"},"modified":"2025-05-24T16:28:36","modified_gmt":"2025-05-24T10:58:36","slug":"using-php-and-cron-to-automate-kannel-status-monitoring","status":"publish","type":"post","link":"https:\/\/pheonixsolutions.com\/blog\/using-php-and-cron-to-automate-kannel-status-monitoring\/","title":{"rendered":"Using PHP and Cron to Automate Kannel Status Monitoring"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">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&#8217;ll also handle common production issues, such as the dreaded &#8220;Invalid response: Kannel status not found&#8221; error, and set up logging to \/var\/log\/kannel_monitor.log for debugging.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why Monitor Kannel?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Kannel\u2019s status page provides real-time metrics, such as:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>SMSC Connections<\/strong>: Details about each SMS center (SMSC), including protocol, IP, ports, and status.<\/li>\n\n\n\n<li><strong>SMS Metrics<\/strong>: Received, sent, queued, and failed messages, with rates (messages per second).<\/li>\n\n\n\n<li><strong>DLR Metrics<\/strong>: Delivery report (DLR) statistics for tracking message delivery.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Our script will fetch this data, parse it, store it in a database, and log operations for troubleshooting.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Designing the Script<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The kannel_monitor.php script will:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Fetch Kannel status data using file_get_contents.<\/li>\n\n\n\n<li>Parse metrics (SMSC connections, SMS, and DLR) using regular expressions.<\/li>\n\n\n\n<li>Store the data in a MySQL database.<\/li>\n\n\n\n<li>Log operations to \/var\/log\/kannel_monitor.log.<\/li>\n\n\n\n<li>Run every 5 minutes via a cron job.<br><\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n\n\/\/ Fetch status text\n$context = stream_context_create(&#091;\n    'ssl' =&gt; &#091;\n        'verify_peer' =&gt; true,\n        'verify_peer_name' =&gt; true\n    ]\n]);\n$statusText = @file_get_contents($kannelUrl, false, $context);\nif ($statusText === false) {\n    $error = error_get_last();\n    error_log(\"Failed to fetch status from $kannelUrl: \" . ($error&#091;'message'] ?? 'Unknown error'), 3, $logFile);\n    exit(1);\n}\n\n\/\/ Debug: Save response to file\nfile_put_contents(__DIR__ . '\/debug_kannel_status.html', $statusText);\n\n\/\/ Validate response\nif (strpos($statusText, 'Kannel bearerbox version') === false &amp;&amp; strpos($statusText, 'SMSC:') === false) {\n    error_log(\"Invalid response: Kannel status not found. Check URL or response format.\", 3, $logFile);\n    exit(1);\n}\n\n\/\/ Initialize metrics\n$smsReceivedTotal = 0;\n$smsSentTotal = 0;\n$smsQueuedReceived = 0;\n$smsQueuedSent = 0;\n$smsInboundRateAvg = 0.0;\n$smsOutboundRateAvg = 0.0;\n$dlrReceivedTotal = 0;\n$dlrSentTotal = 0;\n$dlrQueued = 0;\n$dlrInboundRateAvg = 0.0;\n$smscRecords = &#091;];\n\n\/\/ Parse SMSC connections\npreg_match_all(\n    '\/(\\w+\\&#091;\\w+\\])\\s+(SMPP|HTTP|FAKE):(&#091;^:]+):(\\d+)\\\/(\\d+):(&#091;^:]*):?(SMPP|XCV)?\\s+\\((\\w+)&#091;^,]+,\\s*rcvd: sms (\\d+).*?\\((&#091;\\d.]+),(&#091;\\d.]+),(&#091;\\d.]+)\\).*?sent: sms (\\d+).*?\\((&#091;\\d.]+),(&#091;\\d.]+),(&#091;\\d.]+)\\).*?dlr (\\d+).*?\\((&#091;\\d.]+),(&#091;\\d.]+),(&#091;\\d.]+)\\).*?failed (\\d+), queued (\\d+)\/',\n    $statusText, $smscMatches, PREG_SET_ORDER\n);\nforeach ($smscMatches as $match) {\n    $smscRecords&#091;] = &#091;\n        'provider_name' =&gt; $match&#091;1],\n        'protocol' =&gt; $match&#091;2],\n        'ip_address' =&gt; $match&#091;3] == 'generic' ? null : $match&#091;3],'\n        'smsc_status' =&gt; $match&#091;8],\n        'provider_sms_received' =&gt; (int) $match&#091;9],\n        'provider_dlr_received_rate_avg' =&gt; (float) $match&#091;19],\n        'provider_sms_failed' =&gt; (int) $match&#091;21],\n        'provider_sms_queued' =&gt; (int) $match&#091;22]\n    ];\n}\n\n\/\/ Parse aggregate metrics\nif (preg_match('\/SMS: received (\\d+) \\((\\d+) queued\\), sent (\\d+) \\((\\d+) queued\\)\/', $statusText, $matches)) {\n    $smsReceivedTotal = (int) $matches&#091;1];\n    $smsQueuedReceived = (int) $matches&#091;2];\n    $smsSentTotal = (int) $matches&#091;3];\n    $smsQueuedSent = (int) $matches&#091;4];\n}\nif (preg_match('\/SMS: inbound \\((&#091;\\d.]+),(&#091;\\d.]+),(&#091;\\d.]+)\\) msg\\\/sec, outbound \\((&#091;\\d.]+),(&#091;\\d.]+),(&#091;\\d.]+)\\) msg\\\/sec\/', $statusText, $matches)) {\n    $smsInboundRateAvg = (float) $matches&#091;2];\n    $smsOutboundRateAvg = (float) $matches&#091;5];\n}\nif (preg_match('\/DLR: received (\\d+), sent (\\d+)\/', $statusText, $matches)) {\n    $dlrReceivedTotal = (int) $matches&#091;1];\n    $dlrSentTotal = (int) $matches&#091;2];\n}\nif (preg_match('\/DLR: (\\d+) queued\/', $statusText, $matches)) {\n    $dlrQueued = (int) $matches&#091;1];\n}\nif (preg_match('\/DLR: inbound \\((&#091;\\d.]+),(&#091;\\d.]+),(&#091;\\d.]+)\\) msg\\\/sec\/', $statusText, $matches)) {\n    $dlrInboundRateAvg = (float) $matches&#091;2];\n}\n\n\/\/ Connect to database\n$db = new mysqli($dbHost, $dbUser, $dbPass, $dbName);\nif ($db-&gt;connect_error) {\n    error_log(\"DB connection failed: \" . $db-&gt;connect_error, 3, $logFile);\n    exit(1);\n}\n\n\/\/ Insert records\n$stmt = $db-&gt;prepare(\"\n    INSERT INTO kannel_monitor (\n        timestamp, provider_name, smsc_status, protocol, ip_address, port_transmit, port_receive, system_id,\n        provider_sms_received, provider_sms_sent, provider_dlr_received, provider_sms_failed, provider_sms_queued,\n        provider_sms_received_rate_avg, provider_sms_sent_rate_avg, provider_dlr_received_rate_avg,\n        sms_received_total, sms_sent_total, sms_queued_received, sms_queued_sent,\n        sms_inbound_rate_avg, sms_outbound_rate_avg, dlr_received_total, dlr_sent_total, dlr_queued,\n        dlr_inbound_rate_avg\n    )\n    VALUES (NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n\");\nif (!$stmt) {\n    error_log(\"Prepare failed: \" . $db-&gt;error, 3, $logFile);\n    $db-&gt;close();\n    exit(1);\n}\n\nforeach ($smscRecords as $record) {\n    $stmt-&gt;bind_param(\n        'ssssiiisiiiidddiiiiddiiid',\n        $record&#091;'provider_name'],\n        $record&#091;'smsc_status'],\n        $record&#091;'protocol'],\n        $record&#091;'ip_address'],\n        $record&#091;'port_transmit'],\n        $smsQueuedSent,\n        $smsInboundRateAvg,\n        $smsOutboundRateAvg,\n        $dlrReceivedTotal,\n        $dlrSentTotal,\n        $dlrQueued,\n        $dlrInboundRateAvg\n    );\n    if (!$stmt-&gt;execute()) {\n        error_log(\"Insert failed for {$record&#091;'provider_name']}: \" . $db-&gt;error, 3, $logFile);\n        break;\n    } else {\n        error_log(\"Inserted record for {$record&#091;'provider_name']}\", 3, $logFile);\n    }\n}\n\/\/ Insert aggregate metrics if no SMSC records\nif (empty($smscRecords)) {\n    $nullStr = null;\n    $nullInt = null;\n    $nullFloat = null;\n    $stmt-&gt;bind_param(\n        'ssssiiisiiiidddiiiiddiiid',\n        $nullStr,\n        $nullStr,\n        $nullStr,\n        $nullInt,\n        $nullFloat,\n        $nullFloat,\n        $nullFloat,\n        $smsReceivedTotal,\n        $smsSentTotal,\n        $smsQueuedReceived,\n        $smsQueuedSent,\n        $smsInboundRateAvg,\n        $smsOutboundRateAvg,\n        $dlrReceivedTotal,\n        $dlrSentTotal,\n        $dlrQueued,\n        $dlrInboundRateAvg\n    );\n    if (!$stmt-&gt;execute()) {\n        error_log(\"Insert failed for aggregate metrics: \" . $db-&gt;error, 3, $logFile);\n    } else {\n        error_log(\"Inserted aggregate metrics\", 3, $logFile);\n    }\n}\n\n$stmt-&gt;close();\n$db-&gt;close();\n\nerror_log(\"Data stored successfully\", 3, $logFile)<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: The Script<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"> Complete kannel_monitor.php script, which I developed to monitor a Kannel instance at <a href=\"https:\/\/app.magneri.com\/?kannel\" target=\"_blank\" rel=\"noopener\">https:\/\/example.com\/?kannel<\/a>. It includes error handling, logging, and validation to avoid issues like the &#8220;Invalid response&#8221; error I encountered in production.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Setting Up the Environment<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To run this script in production, follow these steps:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Save the Script<\/strong>: Save the script to <code>\/var\/www\/html\/scripts\/kannel_monitor.php<\/code>:<br>Paste the script above and set permissions: <code>chmod 644 \/var\/www\/html\/scripts\/kannel_monitor.php chown www-data:www-data \/var\/www\/html\/scripts\/kannel_monitor.php<\/code><\/li>\n\n\n\n<li><strong>Create .env File<\/strong>: <code>KANNEL_URL=https:\/\/example.com\/?<\/code><br><code>kannel DB_HOST=localhost <\/code><br><code>DB_USER=your_db_user <\/code><br><code>DB_PASS=your_db_password <\/code><br><code>DB_NAME=your_db_name<\/code><\/li>\n\n\n\n<li><strong>Create the Log File<\/strong>: Set up <code>\/var\/log\/kannel_monitor.log<\/code>: <code>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<\/code><\/li>\n\n\n\n<li><strong>Set Up the Cron Job<\/strong>: Schedule the script to run every 5 minutes: <code>crontab -e<\/code> Add: <code>*\/5 * * * * \/usr\/bin\/php \/var\/www\/html\/scripts\/kannel_monitor.php &gt;&gt; \/var\/log\/kannel_monitor.log 2&gt;&amp;1<\/code> Verify: <code>crontab -l<\/code><\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Testing the Script<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Test the script manually:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/usr\/bin\/php \/var\/www\/html\/scripts\/kannel_monitor.php<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Check the log file:<\/strong><br>cat \/var\/log\/kannel_monitor.log<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected output:<\/strong><br>Inserted record for provider1[smsc1]<br>Inserted aggregate metrics<br>Data stored successfully<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It should contain Kannel status data (e.g., SMSC:, SMS: received).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Verify database records:<\/strong><br>SELECT * FROM kannel_monitor;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wait 5 minutes to confirm the cron job runs, or temporarily set it to run every minute:<br>* * * * \/usr\/bin\/php \/var\/www\/html\/scripts\/kannel_monitor.php &gt;&gt; \/var\/log\/kannel_monitor.log 2&gt;&amp;1<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/pheonixsolutions.com\/blog\/wp-content\/uploads\/2025\/05\/Screenshot-2025-05-24-at-4.16.01\u202fPM.png\"><img fetchpriority=\"high\" decoding=\"async\" width=\"1024\" height=\"410\" src=\"https:\/\/pheonixsolutions.com\/blog\/wp-content\/uploads\/2025\/05\/Screenshot-2025-05-24-at-4.16.01\u202fPM-1024x410.png\" alt=\"\" class=\"wp-image-8983\" srcset=\"https:\/\/pheonixsolutions.com\/blog\/wp-content\/uploads\/2025\/05\/Screenshot-2025-05-24-at-4.16.01\u202fPM-1024x410.png 1024w, https:\/\/pheonixsolutions.com\/blog\/wp-content\/uploads\/2025\/05\/Screenshot-2025-05-24-at-4.16.01\u202fPM-300x120.png 300w, https:\/\/pheonixsolutions.com\/blog\/wp-content\/uploads\/2025\/05\/Screenshot-2025-05-24-at-4.16.01\u202fPM-768x307.png 768w, https:\/\/pheonixsolutions.com\/blog\/wp-content\/uploads\/2025\/05\/Screenshot-2025-05-24-at-4.16.01\u202fPM-1536x615.png 1536w, https:\/\/pheonixsolutions.com\/blog\/wp-content\/uploads\/2025\/05\/Screenshot-2025-05-24-at-4.16.01\u202fPM-750x300.png 750w, https:\/\/pheonixsolutions.com\/blog\/wp-content\/uploads\/2025\/05\/Screenshot-2025-05-24-at-4.16.01\u202fPM.png 1544w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><br>Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Kannel, a robust open-source SMS and WAP gateway, to control SMS traffic. Performance monitoring is essential to guarantee dependable [&hellip;]<\/p>\n","protected":false},"author":514,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[1022],"tags":[],"class_list":["post-8975","post","type-post","status-publish","format-standard","hentry","category-web-architecture","psol-cat-web-architecture"],"jetpack_publicize_connections":[],"jetpack_shortlink":"https:\/\/wp.me\/phn2x7-2kL","jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/pheonixsolutions.com\/blog\/wp-json\/wp\/v2\/posts\/8975","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pheonixsolutions.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pheonixsolutions.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pheonixsolutions.com\/blog\/wp-json\/wp\/v2\/users\/514"}],"replies":[{"embeddable":true,"href":"https:\/\/pheonixsolutions.com\/blog\/wp-json\/wp\/v2\/comments?post=8975"}],"version-history":[{"count":0,"href":"https:\/\/pheonixsolutions.com\/blog\/wp-json\/wp\/v2\/posts\/8975\/revisions"}],"wp:attachment":[{"href":"https:\/\/pheonixsolutions.com\/blog\/wp-json\/wp\/v2\/media?parent=8975"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pheonixsolutions.com\/blog\/wp-json\/wp\/v2\/categories?post=8975"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pheonixsolutions.com\/blog\/wp-json\/wp\/v2\/tags?post=8975"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}