When building software like Icinga, some of the most critical operations happen silently in the background. Recurring tasks whether for collecting data, renewing certificates, or generating reports are essential to delivering the functionality our users rely on every day. Traditionally, many of these tasks are handled by system-level tools like cron. While cron is powerful and reliable, it’s also limited in flexibility and visibility, especially when building complex PHP applications like ours.
That’s why we’ve taken a different path: scheduling recurring tasks directly in PHP. This approach gives us complete control over task execution, integrates more deeply with our application logic, and offers better observability. In this post, we want to share how we do this inside the Icinga R&D team, and show where this approach powers some of our core modules, specifically Icinga Reporting and Icinga Certificate Monitoring.
Rethinking Cron: Why PHP-Based Scheduling?
At first glance, using cron seems like the obvious solution. It’s been around for decades, is available on almost every Linux system, and can execute any script at defined intervals. However, once we began designing more integrated and responsive features in Icinga, cron started to show its limitations. For one, it operates entirely outside the context of your application. It has no access to your application’s internal state, and its environment might differ significantly from the one your application runs in. Handling errors, monitoring success, or retrying failed executions requires additional tools and setup.
By contrast, scheduling tasks within PHP gives us the opportunity to stay entirely inside the application lifecycle. We can use native PHP logic, shared dependencies, error handling mechanisms, and logging infrastructure. It also means that our scheduled tasks can evolve alongside the rest of the application. No need to maintain separate cron scripts or worry about deployment mismatches.
How It Works in Our Codebase
Our solution combines two powerful PHP libraries: ReactPHP and dragonmantank/cron-expression. ReactPHP allows us to run non-blocking, event-driven code, perfect for scheduling timers or repeating jobs without freezing the application. The cron-expression library provides a simple way to parse and calculate the next due times based on familiar cron syntax.
The result is a small but effective mechanism that checks whether a task is due to run, executes it, and then schedules the next execution. This loop continues as long as the application is running, ensuring tasks are handled reliably over time.
This setup is used in multiple places throughout Icinga’s PHP-based modules, and it has proven to be both robust and maintainable. It also makes it easier for developers on our team to write and reason about recurring logic, since everything stays within the same programming model.
<?php namespace Icinga\Blog; use Cron\CronExpression; use DateTime; use React\EventLoop\Loop; /** * Schedule the given task based on the specified CRON expression * * @param callable $task * @param CronExpression $cron */ function schedule(callable $task, CronExpression $cron): void { $now = new DateTime(); // CRON is due, run task asap: if ($cron->isDue($now)) { Loop::futureTick(function () use ($task) { $task(); }); } // Function that executes the task // and adds a timer to the event loop to execute the function again when the task is next due: $schedule = function () use (&$schedule, $task, $cron) { $task(); $now = new DateTime(); $nextDue = $cron->getNextRunDate($now); Loop::addTimer($nextDue->getTimestamp() - $now->getTimestamp(), $schedule); }; // Add a timer to the event loop to execute the task when it is next due: $nextDue = $cron->getNextRunDate($now); Loop::addTimer($nextDue->getTimestamp() - $now->getTimestamp(), $schedule); } // Run example task every minute: $cron = new CronExpression('* * * * *'); $task = function () { echo date('Y-m-d H:i:s') . ": Task running\n"; }; schedule($task, $cron);
Real-World Applications in Icinga
One of the most visible uses of our PHP scheduling approach is in Icinga Reporting. Reports often rely on aggregated data that must be collected at regular intervals. For example, we might query the Icinga backend every hour or every day, depending on the reporting configuration. Instead of relying on cron to trigger those data collection jobs, we use PHP-based tasks that run inside the reporting daemon. This ensures that all the necessary dependencies are already loaded, errors are logged within the module itself, and we can easily retry or delay tasks depending on system load or availability.
Another example is Icinga Certificate Monitoring, which regularly checks the TLS/SSL certificates of monitored services. These checks need to be repeated frequently to ensure timely detection of expiring certificates. Here too, our scheduler determines when each check should run, executes the task using ReactPHP, and reschedules it for the next appropriate interval. Since the task logic lives inside the module, we can easily adapt to edge cases such as temporarily unreachable endpoints or certificates that renew automatically via ACME.
What unites both examples is the consistent use of internal, event-driven scheduling. This gives us not only better control over when and how tasks run, but also deeper integration with the broader Icinga stack—making it easier to debug, test, and extend our features.
Benefits for Users and Developers
For Icinga users, this architecture mostly works quietly in the background. But it brings several advantages: fewer external dependencies, more consistent deployments, and smoother updates when new versions of our modules are released. There’s no need to configure cron separately, and no risk of version mismatches between scheduled scripts and the actual application logic.
For us in R&D, it simplifies both development and support. By consolidating all logic inside the PHP application, we reduce complexity, improve error handling, and streamline our testing processes. It also allows us to focus on improving the features themselves rather than maintaining glue code between systems.
Final Thoughts
By choosing to schedule recurring tasks in PHP, we’ve made a deliberate architectural decision that supports the kind of reliable, integrated monitoring experience Icinga is known for. It’s a solution that continues to serve us well across multiple modules and use cases. And while this approach may not be visible to most users, it’s a core part of what keeps Icinga Reporting and Certificate Monitoring running smoothly and predictably.
As our platform evolves, we’ll continue to refine and expand this scheduling mechanism, possibly adding more intelligence and self-healing capabilities. We hope this insight into our R&D process shows how much thought goes into the smaller pieces of Icinga and how those pieces come together to deliver a strong, dependable monitoring tool.
If you’re building your own extensions or tools with Icinga, or simply want to understand how things work under the hood, we hope this post gave you a helpful starting point.