<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Łukasz Maśląg</title>
    <description>The latest articles on DEV Community by Łukasz Maśląg (@lmadev).</description>
    <link>https://dev.to/lmadev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3644286%2F1c21ed23-0323-46bf-9137-62b220b6f5eb.jpg</url>
      <title>DEV Community: Łukasz Maśląg</title>
      <link>https://dev.to/lmadev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lmadev"/>
    <language>en</language>
    <item>
      <title>Incident Response for Failed Scheduled Tasks</title>
      <dc:creator>Łukasz Maśląg</dc:creator>
      <pubDate>Tue, 03 Feb 2026 17:25:55 +0000</pubDate>
      <link>https://dev.to/cronmonitor/incident-response-for-failed-scheduled-tasks-ikk</link>
      <guid>https://dev.to/cronmonitor/incident-response-for-failed-scheduled-tasks-ikk</guid>
      <description>&lt;p&gt;Scheduled tasks are the silent workhorses of modern applications. They create backups, send reports, synchronize data, and keep the system running smoothly—until they stop working. When a cron job fails without notice, you might not notice it for days or even weeks.&lt;br&gt;
I've experienced this myself many times. The last incident involved a GPS data synchronization failure between an external API and a client instance for several days. The task was configured, the server was running, but data stopped downloading for some stupid reason: the secret key had changed :(. This experience once again made me and the client realize that monitoring isn't optional—it's essential.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Hidden Cost of Silent Failures
&lt;/h2&gt;

&lt;p&gt;Failed scheduled tasks rarely announce themselves. Unlike a crashed web server that immediately frustrates users, a broken cron job just... stops. The damage accumulates quietly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data loss&lt;/strong&gt;: Backups that never ran mean recovery becomes impossible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale information&lt;/strong&gt;: Reports based on outdated data lead to bad decisions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cascade failures&lt;/strong&gt;: One missed sync can break downstream processes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance issues&lt;/strong&gt;: Regulatory requirements often mandate specific automated processes&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Building an Effective Incident Response Plan
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Detection: Know When Something Breaks
&lt;/h3&gt;

&lt;p&gt;The first challenge is knowing a failure occurred. Traditional approaches have significant gaps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log monitoring&lt;/strong&gt; catches errors only if the job runs and logs something. A job that never starts produces no logs to monitor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Heartbeat monitoring&lt;/strong&gt; flips this approach. Instead of watching for failures, you watch for success signals. If your job doesn't check in within its expected window, something's wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# At the end of your cron job, send a heartbeat&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://cronmonitor.app/ping/abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple pattern catches both execution failures and jobs that never started.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Classification: Assess the Impact
&lt;/h3&gt;

&lt;p&gt;Not all failures require the same response. Create a severity matrix:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;Criteria&lt;/th&gt;
&lt;th&gt;Response Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;td&gt;Data loss risk, customer impact&lt;/td&gt;
&lt;td&gt;Immediate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Business process affected&lt;/td&gt;
&lt;td&gt;Within 1 hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Degraded functionality&lt;/td&gt;
&lt;td&gt;Within 4 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Minor inconvenience&lt;/td&gt;
&lt;td&gt;Next business day&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Your backup jobs? Critical. That weekly analytics report? Probably medium.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Notification: Alert the Right People
&lt;/h3&gt;

&lt;p&gt;Effective alerting means reaching the right person through the right channel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Email&lt;/strong&gt; works for low-priority, non-urgent issues&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack/Discord&lt;/strong&gt; suits team-wide visibility and collaborative debugging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram&lt;/strong&gt; offers a good balance of immediacy and unobtrusiveness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid alert fatigue by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grouping related failures&lt;/li&gt;
&lt;li&gt;Setting appropriate thresholds before alerting&lt;/li&gt;
&lt;li&gt;Including actionable information in every alert&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Response: Have a Playbook Ready
&lt;/h3&gt;

&lt;p&gt;When alerts fire at 3 AM, you don't want to be figuring out what to do. Document your runbooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Backup Job Failure Runbook&lt;/span&gt;

&lt;span class="gu"&gt;### Immediate Actions&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Check if the job is currently running: &lt;span class="sb"&gt;`ps aux | grep backup`&lt;/span&gt;
&lt;span class="p"&gt;2.&lt;/span&gt; Review recent logs: &lt;span class="sb"&gt;`tail -100 /var/log/backup.log`&lt;/span&gt;
&lt;span class="p"&gt;3.&lt;/span&gt; Verify disk space: &lt;span class="sb"&gt;`df -h`&lt;/span&gt;
&lt;span class="p"&gt;4.&lt;/span&gt; Check database connectivity

&lt;span class="gu"&gt;### Common Causes&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Disk full → Clear old files, expand storage
&lt;span class="p"&gt;-&lt;/span&gt; Database locked → Check for long-running queries
&lt;span class="p"&gt;-&lt;/span&gt; Network timeout → Verify connectivity to remote storage

&lt;span class="gu"&gt;### Escalation&lt;/span&gt;
If unresolved after 30 minutes, contact: [DBA on-call]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Recovery: Get Back to Normal
&lt;/h3&gt;

&lt;p&gt;Once you've identified the problem:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fix the immediate issue&lt;/strong&gt; - Get the job running again&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the fix&lt;/strong&gt; - Manually trigger the job, confirm success&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check for data gaps&lt;/strong&gt; - Did you miss processing any data?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backfill if needed&lt;/strong&gt; - Run catch-up jobs for missed periods&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  6. Post-Mortem: Learn and Improve
&lt;/h3&gt;

&lt;p&gt;Every incident is a learning opportunity. Document:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happened and when&lt;/li&gt;
&lt;li&gt;How it was detected&lt;/li&gt;
&lt;li&gt;Root cause analysis&lt;/li&gt;
&lt;li&gt;What fixed it&lt;/li&gt;
&lt;li&gt;How to prevent recurrence&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practical Implementation Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Start Simple
&lt;/h3&gt;

&lt;p&gt;You don't need enterprise tooling to monitor cron jobs effectively. Even a basic approach helps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# backup.sh&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;  &lt;span class="c"&gt;# Exit on any error&lt;/span&gt;

/usr/local/bin/do-backup.sh

&lt;span class="c"&gt;# Only reached if backup succeeded&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 10 &lt;span class="nt"&gt;--retry&lt;/span&gt; 5 https://cronmonitor.app/ping/abs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add Context to Your Alerts
&lt;/h3&gt;

&lt;p&gt;An alert saying "Job failed" is less useful than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALERT: Daily backup failed
Server: prod-db-1
Last success: 2026-01-05 02:00 UTC
Expected: Every day at 02:00 UTC
Logs: /var/log/backup/2026-01-06.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test Your Monitoring
&lt;/h3&gt;

&lt;p&gt;Periodically verify your monitoring actually works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Intentionally break a non-critical job&lt;/li&gt;
&lt;li&gt;Confirm the alert fires&lt;/li&gt;
&lt;li&gt;Confirm it reaches the right people&lt;/li&gt;
&lt;li&gt;Confirm the runbook is accurate&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Silent failures are preventable. With proper monitoring, clear escalation paths, and documented runbooks, you transform "we discovered it weeks later" into "we fixed it in minutes."&lt;/p&gt;

&lt;p&gt;The investment in incident response pays off not when everything works, but when something inevitably breaks. And in distributed systems with dozens of scheduled tasks, something always breaks eventually.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Monitoring Cron Jobs in Kubernetes: Why It's Harder Than You Think?</title>
      <dc:creator>Łukasz Maśląg</dc:creator>
      <pubDate>Sat, 17 Jan 2026 16:08:59 +0000</pubDate>
      <link>https://dev.to/cronmonitor/monitoring-cron-jobs-in-kubernetes-why-its-harder-than-you-think-99n</link>
      <guid>https://dev.to/cronmonitor/monitoring-cron-jobs-in-kubernetes-why-its-harder-than-you-think-99n</guid>
      <description>&lt;p&gt;Have you ever missed or simply failed to notice that a job added to a Kubernetes cron job isn't executing? Cron jobs fail in Kubernetes, just like they do on Docker or a physical machine. I've described this in other articles &lt;a href="https://cronmonitor.app/blog/how-to-safely-run-cron-jobs-in-docker" rel="noopener noreferrer"&gt;How to safely run cron jobs in Docker with monitoring&lt;/a&gt;. Therefore, monitoring cron jobs in Kubernetes is just as important, but significantly more difficult than on a physical machine or Docker.&lt;/p&gt;

&lt;p&gt;In this article, I'll discuss the challenges of monitoring cron jobs in Kubernetes, common failures to watch out for, and practical solutions to help you avoid so-called "silent cron failures."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Kubernetes CronJobs Are Different
&lt;/h2&gt;

&lt;p&gt;Unlike traditional cron on a Linux server, Kubernetes CronJobs add multiple layers of abstraction. When a CronJob triggers, Kubernetes creates a Job object, which then spawns one or more Pods to execute the actual work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;batch/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CronJob&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database-backup&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
  &lt;span class="na"&gt;jobTemplate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backup&lt;/span&gt;
            &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
            &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_dump"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-h"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db-host"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-U"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;admin"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mydb"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;restartPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OnFailure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This hierarchy means that failures can occur at three different levels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cron Job Level&lt;/strong&gt; – The scheduler is unable to create the job&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task Level&lt;/strong&gt; – The job times out or exceeds its retry limit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container Level&lt;/strong&gt; – The container exits with an error&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most monitoring tools focus on deployments and services – they simply aren't designed with cron jobs in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Silent Failure" Problem
&lt;/h2&gt;

&lt;p&gt;What makes monitoring cron so difficult: By default, Kubernetes doesn't notify you when cron fails (unlike operating systems like Linux). The job simply fails, Kubernetes logs it, and life goes on—until, often unexpectedly, you notice that backups haven't been performed in weeks or that days of processing are missing from the data pipeline.&lt;/p&gt;

&lt;p&gt;Consider the following real-life scenario: a team's label automation bot was running as a Kubernetes cron job. When the cluster became resource-constrained, the scheduler couldn't launch new pods, and the cron job silently failed for several hours before anyone noticed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Kubernetes CronJob Failure Modes
&lt;/h2&gt;

&lt;p&gt;Before configuring monitoring, it's important to understand what can go wrong:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Resource Limitations
&lt;/h3&gt;

&lt;p&gt;The cluster may not have enough CPU or memory resources to schedule a pod. This is especially common in smaller clusters or during peak load periods.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jobTemplate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;job&lt;/span&gt;
            &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;64Mi"&lt;/span&gt;
                &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;50m"&lt;/span&gt;
              &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;128Mi"&lt;/span&gt;
                &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Wskazówka:&lt;/strong&gt; Utrzymuj niskie żądania zasobów dla crona, ale ustaw odpowiednie limity.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Błędy pobierania obrazów
&lt;/h3&gt;

&lt;p&gt;Błędy &lt;code&gt;ImagePullBackOff&lt;/code&gt; mogą całkowicie uniemożliwić uruchomienie zadania. Często zdarza się to, gdy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rejestr obrazów jest niedostępny&lt;/li&gt;
&lt;li&gt;Nazwy obrazów są błędnie wpisane&lt;/li&gt;
&lt;li&gt;Utracone dane uwierzytelniające&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Konflikty współbieżności
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;concurrencyPolicy&lt;/code&gt; setting determines what happens when a new job should start but the previous one is still running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;concurrencyPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Forbid&lt;/span&gt;  &lt;span class="c1"&gt;# Skip new job if previous is running&lt;/span&gt;
  &lt;span class="c1"&gt;# concurrencyPolicy: Replace  # Kill previous job, start new one&lt;/span&gt;
  &lt;span class="c1"&gt;# concurrencyPolicy: Allow  # Run multiple jobs in parallel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;Forbid&lt;/code&gt;, if your job takes longer than expected, subsequent executions will be skipped silently.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Starting Deadline Exceeded
&lt;/h3&gt;

&lt;p&gt;If &lt;code&gt;startingDeadlineSeconds&lt;/code&gt; is set too low (under 10 seconds), the cronjob-controller might miss executions entirely since it only checks every 10 seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;startingDeadlineSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;  &lt;span class="c1"&gt;# 5 minutes grace period&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Backoff Limit Reached
&lt;/h3&gt;

&lt;p&gt;After too many failures (default: 6 retries), the Job gives up. Kubernetes also permanently suspends Cron after approximately 100 consecutive scheduling failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traditional Monitoring Approaches (And Their Limitations)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Using kube-state-metrics + Prometheus
&lt;/h3&gt;

&lt;p&gt;The community-standard approach involves deploying kube-state-metrics and writing Prometheus queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Time since last successful run
time() - max(kube_job_status_completion_time{job_name=~"my-cronjob.*"} 
  * on(job_name) group_left() kube_job_status_succeeded{job_name=~"my-cronjob.*"} == 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method works well, but requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuring and maintaining Prometheus&lt;/li&gt;
&lt;li&gt;Writing complex PromQL queries for each job&lt;/li&gt;
&lt;li&gt;Configuring Alertmanager rules&lt;/li&gt;
&lt;li&gt;Building Grafana dashboards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For teams already using a full stack of observations, this increases the scope of monitoring. For everyone else, simply knowing whether the backup script has been run is a significant overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kubectl Approach
&lt;/h3&gt;

&lt;p&gt;You can manually check the job status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all CronJobs&lt;/span&gt;
kubectl get cronjobs

&lt;span class="c"&gt;# Check recent jobs&lt;/span&gt;
kubectl get &lt;span class="nb"&gt;jobs&lt;/span&gt; &lt;span class="nt"&gt;--selector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;job-name&lt;span class="o"&gt;=&lt;/span&gt;database-backup

&lt;span class="c"&gt;# View logs from the last run&lt;/span&gt;
kubectl logs job/database-backup-28391400
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is fine for debugging but doesn't scale. You're not going to manually check every Cron every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simpler Approach: Heartbeat Monitoring
&lt;/h2&gt;

&lt;p&gt;Instead of scraping cluster metrics, there's a simpler pattern: have your jobs report their status to an external monitoring service.&lt;/p&gt;

&lt;p&gt;The concept is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure expected schedules for each job&lt;/li&gt;
&lt;li&gt;Jobs ping the monitoring service on success&lt;/li&gt;
&lt;li&gt;If a ping doesn't arrive within the expected window, you get alerted&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's how you'd modify a CronJob to report its status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;batch/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CronJob&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database-backup&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
  &lt;span class="na"&gt;jobTemplate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backup&lt;/span&gt;
            &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
            &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/bin/sh&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
              &lt;span class="s"&gt;# Run the actual backup&lt;/span&gt;
              &lt;span class="s"&gt;pg_dump -h db-host -U admin mydb &amp;gt; /backup/db.sql&lt;/span&gt;

              &lt;span class="s"&gt;# Report success to monitoring&lt;/span&gt;
              &lt;span class="s"&gt;curl -X POST "https://cronmonitor.app/api/v1/ping/YOUR_MONITOR_ID" \&lt;/span&gt;
                &lt;span class="s"&gt;-H "Authorization: Bearer $CRONMONITOR_API_KEY"&lt;/span&gt;
            &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CRONMONITOR_API_KEY&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cronmonitor-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-key&lt;/span&gt;
          &lt;span class="na"&gt;restartPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OnFailure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach catches all failure modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the Pod never starts → no ping → alert&lt;/li&gt;
&lt;li&gt;If the container crashes → no ping → alert&lt;/li&gt;
&lt;li&gt;If the backup command fails → no ping → alert&lt;/li&gt;
&lt;li&gt;If the job runs but is slower than expected → late ping → alert&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Practices for Kubernetes CronJob Monitoring
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Set Appropriate History Limits
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;successfulJobsHistoryLimit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;failedJobsHistoryLimit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep enough history for debugging, but don't let failed Pods accumulate indefinitely.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Use Labels for Organization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backup-system&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
    &lt;span class="na"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Labels make it easier to filter and organize jobs in both Kubernetes and your monitoring dashboards.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Configure Proper Timeouts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;startingDeadlineSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;  &lt;span class="c1"&gt;# Time to start the job&lt;/span&gt;
  &lt;span class="na"&gt;jobTemplate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;activeDeadlineSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;  &lt;span class="c1"&gt;# Maximum runtime (1 hour)&lt;/span&gt;
      &lt;span class="na"&gt;backoffLimit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;  &lt;span class="c1"&gt;# Retry attempts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Don't Ignore Grace Period Warnings
&lt;/h3&gt;

&lt;p&gt;If your CronJob often runs longer than its schedule interval, you'll see warnings about missed executions. This is a signal to either optimize the job or adjust the schedule.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Test Your Monitoring
&lt;/h3&gt;

&lt;p&gt;Intentionally fail a job to verify your alerts work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create job &lt;span class="nt"&gt;--from&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cronjob/database-backup test-failure &lt;span class="nt"&gt;--&lt;/span&gt; /bin/false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Kubernetes Cron are essential for scheduled tasks, but their multi-layered architecture makes monitoring non-trivial. The key insight is that traditional pull-based monitoring (scraping metrics) is complex for ephemeral workloads. Push-based heartbeat monitoring, where jobs actively report their status, is simpler and more reliable.&lt;/p&gt;

&lt;p&gt;Whether you build your own solution or use a service like &lt;a href="https://cronmonitor.app" rel="noopener noreferrer"&gt;CronMonitor&lt;/a&gt;, the important thing is to have &lt;em&gt;something&lt;/em&gt; in place before you discover a critical job has been failing silently for weeks.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>crontab</category>
      <category>devops</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>How to effectively monitor regular backups</title>
      <dc:creator>Łukasz Maśląg</dc:creator>
      <pubDate>Fri, 09 Jan 2026 19:55:37 +0000</pubDate>
      <link>https://dev.to/cronmonitor/how-to-effectively-monitor-regular-backups-3efc</link>
      <guid>https://dev.to/cronmonitor/how-to-effectively-monitor-regular-backups-3efc</guid>
      <description>&lt;p&gt;Imagine the following scenario: you created a script in bash to create a backup of a production database, say, an online store. After creating the script and adding it to crontab, everything worked flawlessly. After some time, say a month, the database became corrupted, for example, due to the installation of a faulty plugin. At that moment, you want to retrieve an updated database backup from last night and discover that the last database backup is from two weeks ago. What happened? Everything was working fine.&lt;/p&gt;

&lt;p&gt;This nightmare scenario is more common than you might think, and perhaps it has even affected you personally. Scripts added to crontab fail without warning, causing so-called "silent errors." They can be caused by a variety of reasons, such as a full disk, permission changes, network timeouts, expired credentials, or simply a typo after a "quick fix."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Unmonitored Backups
&lt;/h2&gt;

&lt;p&gt;Traditional cron jobs have a fundamental flaw: they only report an error when they &lt;em&gt;fail&lt;/em&gt; to run. For example, your backup script might fail:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run successfully but exit with errors&lt;/li&gt;
&lt;li&gt;Exit but generate empty or corrupted files&lt;/li&gt;
&lt;li&gt;Run but take 10 times longer than expected (a sign of problems)&lt;/li&gt;
&lt;li&gt;Skip tables due to permission issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before you know it, your backup retention period might expire—leaving you without any valid backups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Backup Scripts
&lt;/h2&gt;

&lt;p&gt;The solution is simple: your backup script should actively report its status to an external monitor. Here's how to integrate database backups with &lt;a href="https://cronmonitor.app/login?utm_source=devto&amp;amp;utm_medium=referral" rel="noopener noreferrer"&gt;CronMonitor&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  MySQL/MariaDB Backup Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://cronmonitor.app/api/ping/your-unique-id"&lt;/span&gt;
&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/backups/mysql"&lt;/span&gt;
&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d_%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"production"&lt;/span&gt;

&lt;span class="c"&gt;# Signal start&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/start"&lt;/span&gt;

&lt;span class="c"&gt;# Perform backup&lt;/span&gt;
mysqldump &lt;span class="nt"&gt;--single-transaction&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--routines&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--triggers&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.sql.gz"&lt;/span&gt;

&lt;span class="c"&gt;# Check if backup was successful and file is not empty&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.sql.gz"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# Clean old backups (keep last 7 days)&lt;/span&gt;
    find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.sql.gz"&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +7 &lt;span class="nt"&gt;-delete&lt;/span&gt;

    &lt;span class="c"&gt;# Signal success&lt;/span&gt;
    curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/complete"&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c"&gt;# Signal failure&lt;/span&gt;
    curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/fail"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PostgreSQL Backup Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://cronmonitor.app/api/ping/your-unique-id"&lt;/span&gt;
&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/backups/postgres"&lt;/span&gt;
&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d_%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"production"&lt;/span&gt;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/start"&lt;/span&gt;

&lt;span class="c"&gt;# Use custom format for flexibility&lt;/span&gt;
pg_dump &lt;span class="nt"&gt;-Fc&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.dump"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.dump"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# Verify backup integrity&lt;/span&gt;
    pg_restore &lt;span class="nt"&gt;--list&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.dump"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/complete"&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/fail"&lt;/span&gt;
        &lt;span class="nb"&gt;exit &lt;/span&gt;1
    &lt;span class="k"&gt;fi
else
    &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/fail"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-Database Backup with Size Reporting
&lt;/h3&gt;

&lt;p&gt;For more comprehensive monitoring, you can report backup sizes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://cronmonitor.app/api/ping/your-unique-id"&lt;/span&gt;
&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/backups"&lt;/span&gt;
&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;DATABASES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app_production analytics users"&lt;/span&gt;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/start"&lt;/span&gt;

&lt;span class="nv"&gt;FAILED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nv"&gt;TOTAL_SIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

&lt;span class="k"&gt;for &lt;/span&gt;DB &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$DATABASES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;mysqldump &lt;span class="nt"&gt;--single-transaction&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.sql.gz"&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.sql.gz"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;FAILED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Backup failed for: &lt;/span&gt;&lt;span class="nv"&gt;$DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nv"&gt;SIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt;%z &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.sql.gz"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;%s &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.sql.gz"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;TOTAL_SIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;TOTAL_SIZE &lt;span class="o"&gt;+&lt;/span&gt; SIZE&lt;span class="k"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;fi
done

if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$FAILED&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# Report success with metadata&lt;/span&gt;
    curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/complete?msg=Backed%20up%20&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOTAL_SIZE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%20bytes"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/fail"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How to configure CronMonitor to monitor a cron job&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new monitor in CronMonitor for the "backup" job&lt;/li&gt;
&lt;li&gt;Set a projected schedule (e.g., daily at 2:00 AM)&lt;/li&gt;
&lt;li&gt;Configure a grace period—allow enough time for large databases&lt;/li&gt;
&lt;li&gt;Configure alerts via email, Slack, or Discord&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Key backup monitoring settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schedule&lt;/strong&gt;: Match the server's cron schedule exactly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grace period&lt;/strong&gt;: Set it longer than the longest projected backup time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Verify, don't assume
&lt;/h3&gt;

&lt;p&gt;Always verify that the backup file exists and has content. An empty gzipped file is still a "successful" command.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Test restores regularly
&lt;/h3&gt;

&lt;p&gt;Backups are only as good as their restores. Schedule periodic restore tests—and monitor them too.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Monitor backup duration
&lt;/h3&gt;

&lt;p&gt;CronMonitor tracks how long each job takes. A sudden increase in backup time often indicates growing data volume or performance issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Store backups on an external drive/server
&lt;/h3&gt;

&lt;p&gt;Monitoring should include a synchronization step outside the production database server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# After local backup succeeds&lt;/span&gt;
rsync &lt;span class="nt"&gt;-az&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt; remote:/backups/ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/complete"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/fail"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Document recovery procedures
&lt;/h3&gt;

&lt;p&gt;For example, if you receive an alert on Slack at night about a failed backup attempt, you'll need clear steps on what to do next, not a debugging session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Database backups are the last line of defense against data loss. They deserve more than a cron job and the hope that everything works and executes correctly as planned. Active monitoring provides immediate information about the problem – while you still have time to resolve it.&lt;/p&gt;

&lt;p&gt;Start monitoring your backup scripts today. Your future self (the one who doesn't have to explain data loss to a client) will thank you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://cronmonitor.app?utm_source=dev.to&amp;amp;utm_medium=referral"&gt;CronMonitor&lt;/a&gt; is a simple, developer-friendly cron job monitoring service. Set up your first monitor in under a minute.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>database</category>
      <category>crontab</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>I Launched CronMonitor on Product Hunt Today 🚀</title>
      <dc:creator>Łukasz Maśląg</dc:creator>
      <pubDate>Wed, 07 Jan 2026 10:27:41 +0000</pubDate>
      <link>https://dev.to/cronmonitor/i-launched-cronmonitor-on-product-hunt-today-26k5</link>
      <guid>https://dev.to/cronmonitor/i-launched-cronmonitor-on-product-hunt-today-26k5</guid>
      <description>&lt;p&gt;Hey dev.to community! 👋&lt;/p&gt;

&lt;p&gt;Today's a big day for me - I just launched &lt;a href="https://www.producthunt.com/posts/cronmonitor-app" rel="noopener noreferrer"&gt;CronMonitor&lt;/a&gt; on Product Hunt, and I wanted to share the journey with you all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem That Started It All 😓
&lt;/h2&gt;

&lt;p&gt;Picture this: You wake up, coffee in hand, ready to tackle the day. Then disaster strikes - you need to restore a database backup. You go to grab it and... nothing. The backup job hasn't run in two weeks. No alerts. No notifications. Just silent failure.&lt;/p&gt;

&lt;p&gt;That was my reality. And it hurt.&lt;/p&gt;

&lt;p&gt;Cron jobs are amazing - they run our backups, clean up logs, send reports, sync data. But they have one fatal flaw: &lt;strong&gt;they fail silently&lt;/strong&gt;. No one knows until it's too late.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter CronMonitor 🎯
&lt;/h2&gt;

&lt;p&gt;After that painful lesson, I decided to build something to prevent it from happening again. CronMonitor is a simple tool that ensures you know immediately when your scheduled tasks don't run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚡ Instant alerts via Slack, Discord, or Email when jobs fail&lt;/li&gt;
&lt;li&gt;🚫 No agent installation required - just ping our API&lt;/li&gt;
&lt;li&gt;📊 Dashboard with job history and statistics&lt;/li&gt;
&lt;li&gt;🎨 Terminal-inspired UI (because we're developers!)&lt;/li&gt;
&lt;li&gt;🐳 Works everywhere: Docker, Linux, Windows Task Scheduler&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Tech Stack 🛠️
&lt;/h2&gt;

&lt;p&gt;Building CronMonitor was a great learning experience. Here's what powers it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Symfony 7&lt;/strong&gt; (PHP 8.4) - Robust framework for enterprise-grade reliability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doctrine ORM&lt;/strong&gt; - Database management and migrations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Messenger Component&lt;/strong&gt; - Async processing for alerts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; - Reliable data storage for monitoring records&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; - Caching and fast data access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RabbitMQ&lt;/strong&gt; - Queue management for notification delivery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt; - Consistent environments from dev to production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nginx&lt;/strong&gt; - Web server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Frontend:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terminal-inspired dark theme with &lt;strong&gt;Dracula color palette&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Built for developers who live in the terminal&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next? 🚀
&lt;/h2&gt;

&lt;p&gt;Today's Product Hunt launch is just the beginning. I'm planning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More integration options (PagerDuty, Telegram, etc.)&lt;/li&gt;
&lt;li&gt;Advanced analytics&lt;/li&gt;
&lt;li&gt;Team collaboration features&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Out! 🎁
&lt;/h2&gt;

&lt;p&gt;If you're managing cron jobs, I'd love for you to try CronMonitor:&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://cronmonitor.app?utm_source=dev.to&amp;amp;utm_medium=social"&gt;cronmonitor.app&lt;/a&gt;&lt;br&gt;
🚀 &lt;strong&gt;Product Hunt&lt;/strong&gt;: &lt;a href="https://www.producthunt.com/posts/cronmonitor-app" rel="noopener noreferrer"&gt;Vote &amp;amp; share feedback!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  I'd Love Your Feedback 💬
&lt;/h2&gt;

&lt;p&gt;As a fellow developer, your feedback means the world to me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have you dealt with silent cron failures?&lt;/li&gt;
&lt;li&gt;How do you currently monitor your scheduled tasks?&lt;/li&gt;
&lt;li&gt;What features would make CronMonitor useful for you?&lt;/li&gt;
&lt;li&gt;Any questions about the tech stack or implementation?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop a comment below or check out the Product Hunt page - I'm here all day answering quest&lt;/p&gt;

</description>
      <category>devops</category>
      <category>monitoring</category>
      <category>showdev</category>
      <category>startup</category>
    </item>
    <item>
      <title>Environment variables not working with CRON?</title>
      <dc:creator>Łukasz Maśląg</dc:creator>
      <pubDate>Sun, 04 Jan 2026 17:35:06 +0000</pubDate>
      <link>https://dev.to/cronmonitor/environment-variables-not-working-with-cron-443f</link>
      <guid>https://dev.to/cronmonitor/environment-variables-not-working-with-cron-443f</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;For example, you've written a script to create a database backup. You execute your script from the terminal in your favorite shell, such as bash, zsh, or another shell. Everything works fine. The database dump is completed as expected.&lt;br&gt;
Then you add the job to cron, e.g., to execute it daily at 2 a.m.&lt;br&gt;
You check the next day, and oops, the backup wasn't completed. Why?&lt;br&gt;
Welcome to the #1 nightmare of cron job debugging: missing environment variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cron is running with a minimal environment&lt;/li&gt;
&lt;li&gt;PATH is practically empty&lt;/li&gt;
&lt;li&gt;No user shell variables&lt;/li&gt;
&lt;li&gt;Different working directory&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this happens
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Comparison of cron and shell environments&lt;/li&gt;
&lt;li&gt;Differences between /bin/sh and /bin/bash&lt;/li&gt;
&lt;li&gt;Security considerations (actually good!)&lt;/li&gt;
&lt;li&gt;Code example showing the difference&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Solution 1: Hardcoded paths (bad, but works)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Instead of:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* 2 * * * ./backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Execute:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* 2 * * * /full/path/backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Solution 2: Source environment in the Crontab file
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Load the environment
SHELL=/bin/bash
* * * * * source ~/.bashrc &amp;amp;&amp;amp; /full/path/backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Solution 3: Script-level environment (better solution)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/bin:/usr/bin:/bin
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DATABASE_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DATABASE_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DATABASE_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;
/full/path/backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Solution 4: Use .env files (best for production)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* * * * * cd /full/path/ &amp;amp;&amp;amp; /usr/bin/env $(cat .env) backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Always use absolute paths&lt;/li&gt;
&lt;li&gt;Set the required environment variables in your cron job or script&lt;/li&gt;
&lt;li&gt;Log everything (especially crashes!)&lt;/li&gt;
&lt;li&gt;Test in a minimal environment first&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Environment variables are the &lt;strong&gt;most common cause of "works for me :)" cron errors&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution Rating:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🥇 &lt;strong&gt;Best:&lt;/strong&gt; .env file + absolute paths + monitoring&lt;/li&gt;
&lt;li&gt;🥈 &lt;strong&gt;Good:&lt;/strong&gt; Script-level environment configuration&lt;/li&gt;
&lt;li&gt;🥉 &lt;strong&gt;OK:&lt;/strong&gt; Hardcoded in the script (in simple cases)&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Avoid:&lt;/strong&gt; Relying on the user's shell environment&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Never miss a failed cron job again
&lt;/h3&gt;

&lt;p&gt;Environmental problems are just &lt;strong&gt;one way&lt;/strong&gt; cron jobs can fail. A bigger problem is when you think the job is running at the scheduled time, but in fact, nothing happens.&lt;/p&gt;

&lt;p&gt;For this reason, I wrote a simple cron job monitoring app, &lt;strong&gt;&lt;a href="https://cronmonitor.app?utm_source=devto&amp;amp;utm_medium=referral" rel="noopener noreferrer"&gt;CronMonitor&lt;/a&gt;&lt;/strong&gt;, to solve the problem of "Silent CRON Failures":&lt;/p&gt;

&lt;p&gt;With the app, you'll get:&lt;/p&gt;

&lt;p&gt;✅ Instant alerts when a job fails to run.&lt;br&gt;
✅ Job execution statistics&lt;br&gt;
✅ Free plan forever&lt;/p&gt;




&lt;p&gt;&lt;em&gt;For more cron debugging tips, check out: &lt;a href="https://cronmonitor.app/blog/how-debug?utm_source=devto&amp;amp;utm_medium=referral" rel="noopener noreferrer"&gt;How to debug cron jobs&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>bash</category>
      <category>devops</category>
      <category>linux</category>
    </item>
    <item>
      <title>Handling Timezone Issues in Cron Jobs (2025 Guide)</title>
      <dc:creator>Łukasz Maśląg</dc:creator>
      <pubDate>Fri, 02 Jan 2026 16:51:19 +0000</pubDate>
      <link>https://dev.to/cronmonitor/handling-timezone-issues-in-cron-jobs-2025-guide-52ii</link>
      <guid>https://dev.to/cronmonitor/handling-timezone-issues-in-cron-jobs-2025-guide-52ii</guid>
      <description>&lt;p&gt;Troubleshooting Time Zone Issues in Cron Jobs&lt;br&gt;
How to deal with different time zones when configuring crontab jobs.&lt;/p&gt;

&lt;p&gt;Adding a job to a crontab seems simple at first glance, but problems can arise when the job is scheduled to run at the same time but in different time zones.&lt;/p&gt;

&lt;p&gt;Jobs suddenly start an hour early, reports are generated overnight, or backups overlap after daylight saving time or winter time.&lt;/p&gt;

&lt;p&gt;If you've ever asked yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Why does the job run twice?"&lt;/li&gt;
&lt;li&gt;"Why doesn't the job run at all?"&lt;/li&gt;
&lt;li&gt;"Why does the production environment behave differently than the test environment?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;—this article is for you.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. The Main Problem: Cron Runs in Server Time, Not Real Time
&lt;/h2&gt;

&lt;p&gt;By default, cron uses the server's time zone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;date
&lt;/span&gt;Thursday, January 2, 2026, 7:00 PM CET
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your server is in the &lt;strong&gt;UTC&lt;/strong&gt;, &lt;strong&gt;CET&lt;/strong&gt;, &lt;strong&gt;PST&lt;/strong&gt; time zone... but the application's business logic assumes a completely different time zone.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Silent Killer: Daylight Saving Time (DST)
&lt;/h2&gt;

&lt;p&gt;DST causes two critical problems:&lt;/p&gt;

&lt;h3&gt;
  
  
  ❌ The job runs twice
&lt;/h3&gt;

&lt;p&gt;When we turn back the clocks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;02:00 → 01:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A cron job scheduled for &lt;code&gt;01:30&lt;/code&gt; may run twice.&lt;/p&gt;

&lt;h3&gt;
  
  
  ❌ The job never runs
&lt;/h3&gt;

&lt;p&gt;When we set the clocks forward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;02:00 → 03:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cron job scheduled for &lt;code&gt;02:30&lt;/code&gt; never runs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Cron doesn't have built-in daylight saving time (DST) support.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Anti-Patterns to Avoid
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❌ Scheduling Business Logic Directly in Cron
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 9 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; send_daily_report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This assumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;server time zone = company time zone&lt;/li&gt;
&lt;li&gt;daylight saving time never changes&lt;/li&gt;
&lt;li&gt;the environment is consistent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;All assumptions are incorrect.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ❌ Changing the server time zone for each application
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;timedatectl set-timezone Europe/Warsaw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This breaks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;logs&lt;/li&gt;
&lt;li&gt;monitoring&lt;/li&gt;
&lt;li&gt;containers&lt;/li&gt;
&lt;li&gt;multi-tenant servers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. The Golden Rule (IT Standard)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Always run cron in UTC. Handling time zones in application code.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;How they do it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud providers&lt;/li&gt;
&lt;li&gt;Banks&lt;/li&gt;
&lt;li&gt;SaaS platforms&lt;/li&gt;
&lt;li&gt;Monitoring tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Do it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Correct architecture for time zone-safe cron jobs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ✅ Step 1: Server and cron → UTC
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;timedatectl set-timezone UTC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cron:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; php bin/console cron:runner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cron runs frequently, not "on a schedule."&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Step 2: The application decides when to run
&lt;/h3&gt;

&lt;p&gt;In your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$nowUtc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeImmutable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'now'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'UTC'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nv"&gt;$jobTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeImmutable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'09:00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Europa/Warszawa'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$nowUtc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setTimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jobTime&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTimezone&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'H:i'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'09:00'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nf"&gt;runJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✔ Daylight Saving Time&lt;br&gt;
✔ Predictable&lt;br&gt;
✔ Testable&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Store Schedules with Time Zone Metadata
&lt;/h2&gt;

&lt;p&gt;Never store just &lt;code&gt;cron_expression&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  ❌ Bad
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"cron"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 9 * * *"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  ✅ Good
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"cron"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 9 * * *"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Europe/Warsaw"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Your scheduler must:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Convert &lt;code&gt;now()&lt;/code&gt; → job timezone&lt;/li&gt;
&lt;li&gt;Run the cron job&lt;/li&gt;
&lt;li&gt;Run once&lt;/li&gt;
&lt;li&gt;Block execution&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  7. Idempotence: Protecting against duplicate executions
&lt;/h2&gt;

&lt;p&gt;Daylight saving time or retries can still cause duplicates.&lt;/p&gt;

&lt;p&gt;Always use execution locks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schedule_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or distributed locks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;Database lock&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guarantees: &lt;strong&gt;The job will run at most once on schedule.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Logging: Always log in UTC time
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[2026-01-02T18:00:00Z] Job executed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Convert only in the UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;18:00 UTC → 19:00 CET
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This saves hours of debugging.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Monitoring and Alerting
&lt;/h2&gt;

&lt;p&gt;Timezone errors are invisible without monitoring.&lt;/p&gt;

&lt;p&gt;You should detect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Task not executed&lt;/li&gt;
&lt;li&gt;❌ Task executed twice&lt;/li&gt;
&lt;li&gt;❌ Task executed late&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And notify about it via:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email&lt;/li&gt;
&lt;li&gt;Slack/Discord&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. Checklist: Configuring cron for production
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ Server time zone = UTC&lt;/li&gt;
&lt;li&gt;✅ Application decides execution time&lt;/li&gt;
&lt;li&gt;✅ Time zone saved for each task&lt;/li&gt;
&lt;li&gt;✅ Logic for daylight saving time (DST)&lt;/li&gt;
&lt;li&gt;✅ Idempotent execution&lt;/li&gt;
&lt;li&gt;✅ UTC logs&lt;/li&gt;
&lt;li&gt;✅ Monitoring and alerting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Cron itself is inherently "dumb." This isn't a flaw—it's your responsibility as an engineer.&lt;/p&gt;

&lt;p&gt;If your system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operates in multiple countries&lt;/li&gt;
&lt;li&gt;Has users in different time zones&lt;/li&gt;
&lt;li&gt;Performs critical tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;➡️ &lt;strong&gt;Time zone-aware scheduling is not optional.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want to monitor cron jobs and get notifications when they fail? Check out &lt;a href="https://cronmonitor.app" rel="noopener noreferrer"&gt;CronMonitor.app&lt;/a&gt;—simple monitoring that notifies you when something goes wrong.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cron</category>
      <category>linux</category>
      <category>devops</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>The History of Cron: From Unix Daemon to Modern Monitoring Tools</title>
      <dc:creator>Łukasz Maśląg</dc:creator>
      <pubDate>Wed, 31 Dec 2025 13:11:40 +0000</pubDate>
      <link>https://dev.to/cronmonitor/the-history-of-cron-from-unix-daemon-to-modern-monitoring-tools-9io</link>
      <guid>https://dev.to/cronmonitor/the-history-of-cron-from-unix-daemon-to-modern-monitoring-tools-9io</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbgnso0lrvf3yij9gxhv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbgnso0lrvf3yij9gxhv.webp" alt=" " width="800" height="368"&gt;&lt;/a&gt;&lt;br&gt;
Cron is a Unix-like system tool for scheduling background tasks, derived from the cron daemon. It was invented by Ken Thompson in the 1970s at Bell Labs as part of the Unix system.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Creator and Early Days
&lt;/h2&gt;

&lt;p&gt;Ken Thompson, creator of Unix, developed cron in the early 1970s to automate repetitive system tasks. Version 7 Unix from 1977 formally incorporated cron into the system.&lt;/p&gt;

&lt;p&gt;The name "crontab" comes from "cron table," and "cron" derives from the Greek "chronos" (time). In the 1980s, Paul Vixie expanded its functionality by introducing per-user crontabs and the modern format (minute, hour, day of month, month, day of week).&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Cron and Why Is It Still Important?
&lt;/h2&gt;

&lt;p&gt;In 2025, despite dozens of modern automation tools, cron remains the foundation of DevOps operations. Its simplicity and reliability mean millions of servers worldwide use it - from small projects to Fortune 500 infrastructures.&lt;/p&gt;

&lt;p&gt;Every day, cron:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates database backups&lt;/li&gt;
&lt;li&gt;Cleans logs and temporary files&lt;/li&gt;
&lt;li&gt;Sends reports and notifications&lt;/li&gt;
&lt;li&gt;Runs maintenance scripts&lt;/li&gt;
&lt;li&gt;Synchronizes data between systems&lt;/li&gt;
&lt;li&gt;Generates business reports&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  How Does Cron Work?
&lt;/h2&gt;

&lt;p&gt;Cron operates as a daemon (background process) that checks each user's crontab file every minute. When it finds a task scheduled for the current time, it executes the appropriate command.&lt;/p&gt;
&lt;h3&gt;
  
  
  Crontab Syntax
&lt;/h3&gt;

&lt;p&gt;A crontab entry consists of six fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* * * * * /path/to/command
│ │ │ │ │
│ │ │ │ └─── day of week (0-7, where 0 and 7 are Sunday)
│ │ │ └───── month (1-12)
│ │ └─────── day of month (1-31)
│ └───────── hour (0-23)
└─────────── minute (0-59)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Practical Examples:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Database backup every day at 2:30 AM&lt;/span&gt;
30 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /scripts/backup-database.sh

&lt;span class="c"&gt;# Log cleanup every hour&lt;/span&gt;
0 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /scripts/cleanup-logs.sh

&lt;span class="c"&gt;# Sales report every Monday at 9:00 AM&lt;/span&gt;
0 9 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 1 /scripts/weekly-sales-report.sh

&lt;span class="c"&gt;# Data sync every 15 minutes&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;/15 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /scripts/sync-data.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Managing Crontab
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Edit your crontab&lt;/span&gt;
crontab &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# Display current jobs&lt;/span&gt;
crontab &lt;span class="nt"&gt;-l&lt;/span&gt;

&lt;span class="c"&gt;# Remove all jobs&lt;/span&gt;
crontab &lt;span class="nt"&gt;-r&lt;/span&gt;

&lt;span class="c"&gt;# Edit another user's crontab (root)&lt;/span&gt;
crontab &lt;span class="nt"&gt;-u&lt;/span&gt; username &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advantages of Cron
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Simplicity and Minimal Overhead&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Cron is incredibly simple - no complicated configurations, GUI, or dependencies. One line in a text file is all it takes. This makes it ideal for quick automation deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Built into Every Unix/Linux System&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You don't need to install anything. Cron is wherever Linux is - on servers, in Docker containers, on Raspberry Pi, in WSL on Windows.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Reliability and Stability&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Cron has been running since the 1970s, and its code is exceptionally stable. No memory leaks, crashes, or unexpected behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Minimal Resource Consumption&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The cron daemon uses just a few megabytes of memory. In comparison, modern cloud solutions may require entire infrastructures.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;strong&gt;Complete Control&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You have absolute control over what executes and when. No API limits, no per-invocation charges, no dependencies on external services.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. &lt;strong&gt;Support for Complex Schedules&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Despite simple syntax, cron allows very precise scheduling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Every other day at 3:15&lt;/span&gt;
15 3 &lt;span class="k"&gt;*&lt;/span&gt;/2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /script.sh

&lt;span class="c"&gt;# Wednesdays and Fridays on the hour&lt;/span&gt;
0 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 3,5 /script.sh

&lt;span class="c"&gt;# First day of each quarter&lt;/span&gt;
0 0 1 1,4,7,10 &lt;span class="k"&gt;*&lt;/span&gt; /quarterly-report.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Disadvantages of Cron
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;No Built-in Monitoring&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is cron's biggest problem. When a task fails to execute or ends with an error, cron stays silent. You won't get a notification, won't see in a dashboard that something went wrong. Your backup could have been failing for a month, and you'll only find out when you need to restore data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Task can fail silently&lt;/span&gt;
0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /backup.sh  &lt;span class="c"&gt;# What if backup.sh returns an error?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;strong&gt;Complicated Debugging&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When a cron job doesn't work, debugging is a nightmare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cron runs in a limited environment (different PATH, USER, HOME variables)&lt;/li&gt;
&lt;li&gt;No interactive output&lt;/li&gt;
&lt;li&gt;Logs often scattered across the system (&lt;code&gt;/var/log/syslog&lt;/code&gt;, &lt;code&gt;/var/log/cron&lt;/code&gt;, mail)&lt;/li&gt;
&lt;li&gt;Permission issues are difficult to diagnose
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Works in terminal but not in cron? Typical problem:&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; python script.py  &lt;span class="c"&gt;# Fail - which Python version?&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /usr/bin/python3 /full/path/to/script.py  &lt;span class="c"&gt;# OK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. &lt;strong&gt;Limited Time Precision&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Minimum resolution is one minute. If you need to run a task every few seconds, cron isn't for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# You can't do:&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /script.sh  &lt;span class="c"&gt;# Every 30 seconds - impossible!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. &lt;strong&gt;No Dependency Management&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Cron doesn't know about dependencies between tasks. If one task must complete before another starts, you must implement this yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;strong&gt;Concurrent Execution Problems&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If the previous execution is still running, cron will start the next one. This can lead to problems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Every minute - but what if previous task runs for 2 minutes?&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /long-running-script.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solution requires additional code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; flock &lt;span class="nt"&gt;-n&lt;/span&gt; /tmp/script.lock /long-running-script.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. &lt;strong&gt;No Centralization in Distributed Infrastructure&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In multi-server environments, managing crons becomes chaotic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different crontabs on different machines&lt;/li&gt;
&lt;li&gt;No central "what's happening" view&lt;/li&gt;
&lt;li&gt;Difficult deployment and updates&lt;/li&gt;
&lt;li&gt;Impossible to quickly disable all jobs in emergency situations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Alternatives to Cron
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Systemd Timers - Modern Linux
&lt;/h3&gt;

&lt;p&gt;Systemd timers are cron's successor in modern Linux distributions. They offer much more capability:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integration with systemd ecosystem&lt;/li&gt;
&lt;li&gt;Precise scheduling (seconds, microseconds)&lt;/li&gt;
&lt;li&gt;Logs in journald (&lt;code&gt;journalctl&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Dependency management between services&lt;/li&gt;
&lt;li&gt;Option to run tasks at system startup&lt;/li&gt;
&lt;li&gt;Execution time randomization (load balancing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/systemd/system/backup.timer
&lt;/span&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Daily Backup Timer&lt;/span&gt;

&lt;span class="nn"&gt;[Timer]&lt;/span&gt;
&lt;span class="py"&gt;OnCalendar&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;daily&lt;/span&gt;
&lt;span class="py"&gt;OnCalendar&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;02:30:00&lt;/span&gt;
&lt;span class="py"&gt;Persistent&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;timers.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/systemd/system/backup.service
&lt;/span&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Database Backup&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/scripts/backup.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When to Choose:&lt;/strong&gt; If you work only with Linux and want better system integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jenkins, GitLab CI, GitHub Actions - CI/CD as Scheduler
&lt;/h3&gt;

&lt;p&gt;CI/CD tools can be used as advanced schedulers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excellent visibility (UI, logs, history)&lt;/li&gt;
&lt;li&gt;Code integration (scheduled pipelines)&lt;/li&gt;
&lt;li&gt;Secrets management&lt;/li&gt;
&lt;li&gt;Retry mechanism&lt;/li&gt;
&lt;li&gt;Notifications (email, Slack, MS Teams)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;GitHub Actions Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Daily Backup&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;30&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Manual trigger option&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run backup&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/backup.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When to Choose:&lt;/strong&gt; When tasks are code-related and you want everything in one place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud-Native Solutions: AWS EventBridge, GCP Cloud Scheduler, Azure Functions
&lt;/h3&gt;

&lt;p&gt;Cloud solutions offer scalability and cloud ecosystem integration:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS EventBridge Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schedule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cron(30 2 * * ? *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"arn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:lambda:region:account:function:backup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;action&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;backup&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scalability&lt;/li&gt;
&lt;li&gt;Integration with other cloud services&lt;/li&gt;
&lt;li&gt;High availability (SLA 99.9%+)&lt;/li&gt;
&lt;li&gt;Automatic retry&lt;/li&gt;
&lt;li&gt;Out-of-the-box monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost (can be high with many invocations)&lt;/li&gt;
&lt;li&gt;Vendor lock-in&lt;/li&gt;
&lt;li&gt;Complexity in simple use cases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Airflow, Prefect, Dagster - Workflow Orchestration
&lt;/h3&gt;

&lt;p&gt;For complex pipelines with task dependencies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apache Airflow Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;airflow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DAG&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;airflow.operators.bash&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BashOperator&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="n"&gt;dag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;daily_backup&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;schedule_interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;30 2 * * *&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;catchup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;backup_db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BashOperator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;backup_database&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bash_command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/scripts/backup.sh&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dag&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When to Choose:&lt;/strong&gt; When you have complex workflows with dependencies, conditional logic, and need advanced monitoring.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kubernetes CronJobs
&lt;/h3&gt;

&lt;p&gt;In containerized environments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;batch/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CronJob&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backup&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
  &lt;span class="na"&gt;jobTemplate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backup&lt;/span&gt;
            &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backup-image:latest&lt;/span&gt;
            &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/scripts/backup.sh"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;restartPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OnFailure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When to Choose:&lt;/strong&gt; When you're already working in Kubernetes and want a consistent environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Crontab
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cron Won't Disappear
&lt;/h3&gt;

&lt;p&gt;Despite 50 years of history, cron won't disappear anytime soon. Why?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Backward Compatibility&lt;/strong&gt; - Millions of scripts and systems rely on cron&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt; - Nothing beats &lt;code&gt;* * * * * /script.sh&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Universality&lt;/strong&gt; - Works wherever Unix/Linux exists&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Cost&lt;/strong&gt; - No per-invocation charges or infrastructure requirements&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Trend: Hybrid Approach
&lt;/h3&gt;

&lt;p&gt;The future isn't "cron vs. the rest," but intelligent combination:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 1: Cron + Monitoring&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Cron executes the task&lt;/span&gt;
0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /scripts/backup.sh

&lt;span class="c"&gt;# Backup.sh reports status to monitoring service&lt;/span&gt;
&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# ... backup logic ...&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://cronmonitor.app/api/ping/abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cron's simplicity&lt;/li&gt;
&lt;li&gt;Monitoring and alerts from external service&lt;/li&gt;
&lt;li&gt;Execution history&lt;/li&gt;
&lt;li&gt;Notifications when something goes wrong&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scenario 2: Cron as Fallback&lt;/strong&gt;&lt;br&gt;
Main tasks in modern scheduler (Kubernetes, cloud), but critical backups also in cron as a safety net.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 3: Edge Computing&lt;/strong&gt;&lt;br&gt;
IoT, edge devices, Raspberry Pi - cron remains king in resource-constrained environments.&lt;/p&gt;
&lt;h3&gt;
  
  
  Evolution of Cron-Adjacent Tools
&lt;/h3&gt;

&lt;p&gt;Instead of replacing cron, tools emerge that complement it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring-as-a-Service&lt;/strong&gt; - Services like CronMonitor, Cronitor, Healthchecks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cron UI&lt;/strong&gt; - Web interfaces for crontab management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cron Parsers&lt;/strong&gt; - Tools for syntax validation and translation (crontab.guru)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrapper Scripts&lt;/strong&gt; - Smart scripts wrapping cron jobs with additional logic&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  What's Changing in Usage?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;From:&lt;/strong&gt; "Set and forget"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /backup.sh  &lt;span class="c"&gt;# And hope it works&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;To:&lt;/strong&gt; "Set, monitor, react"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /backup.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl https://monitor.app/success &lt;span class="o"&gt;||&lt;/span&gt; curl https://monitor.app/fail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Future:&lt;/strong&gt; Automatic Self-Healing&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cron job fails&lt;/li&gt;
&lt;li&gt;Monitoring system detects it&lt;/li&gt;
&lt;li&gt;Automatic retry with exponential backoff&lt;/li&gt;
&lt;li&gt;Escalation to human only if multiple attempts fail&lt;/li&gt;
&lt;li&gt;Self-diagnostic logs for faster debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cron in Containers and Cloud
&lt;/h3&gt;

&lt;p&gt;Containerization doesn't kill cron - it transforms it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Cron in Docker - still a popular pattern&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:22.04&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; cron
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; crontab /etc/cron.d/app-cron&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;0644 /etc/cron.d/app-cron
&lt;span class="k"&gt;RUN &lt;/span&gt;crontab /etc/cron.d/app-cron
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["cron", "-f"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But a new trend also emerges: &lt;strong&gt;sidecar cron containers&lt;/strong&gt; - separate containers solely for scheduling that call main application containers via API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Upcoming Challenges
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt; - Growing monitoring requirements (OpenTelemetry, distributed tracing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance&lt;/strong&gt; - Execution audit and logs for regulatory requirements (GDPR, SOC2)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Cloud&lt;/strong&gt; - Managing crons in hybrid environments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt; - Protection against privilege escalation through cron jobs&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cron Monitoring - The Key to Peace of Mind
&lt;/h2&gt;

&lt;p&gt;Regardless of whether you use pure cron, systemd timers, or Kubernetes CronJobs, monitoring is absolutely critical. It's the difference between "I hope it works" and "I know it works."&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Is Monitoring Essential?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A Real-Life Story:&lt;/strong&gt;&lt;br&gt;
An e-commerce company had a cron job to generate sales reports every night at 2:00 AM. One day, the script stopped working due to a database structure change. Nobody noticed for 3 months - until accounting started preparing annual statements. The missing data cost the company thousands of euros in reconstruction work.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Should a Good Monitoring System Track?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Heartbeat Monitoring&lt;/strong&gt; - Did the task execute at all?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Success/Failure Detection&lt;/strong&gt; - Did it complete successfully?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution Time Tracking&lt;/strong&gt; - Is it taking unusually long/short?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alert Management&lt;/strong&gt; - Email, Slack, Discord notifications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution History&lt;/strong&gt; - What happened in the past?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dashboard&lt;/strong&gt; - Quick overview of all task statuses&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  How Does This Look in Practice?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Without Monitoring:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /scripts/backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;You pray it works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Simple Monitoring:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /scripts/backup.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl https://cronmonitor.app/ping/abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;You get an alert when it stops working.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Advanced Monitoring:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /scripts/backup-wrapper.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# backup-wrapper.sh&lt;/span&gt;

&lt;span class="c"&gt;# Signal start&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://cronmonitor.app/ping/abc123/start

&lt;span class="nv"&gt;START_TIME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Run actual backup&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; /scripts/backup.sh&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;END_TIME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;DURATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;END_TIME &lt;span class="o"&gt;-&lt;/span&gt; START_TIME&lt;span class="k"&gt;))&lt;/span&gt;

    &lt;span class="c"&gt;# Report success with metrics&lt;/span&gt;
    curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://cronmonitor.app/ping/abc123/success &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"duration=&lt;/span&gt;&lt;span class="nv"&gt;$DURATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c"&gt;# Report failure&lt;/span&gt;
    curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://cronmonitor.app/ping/abc123/fail &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"error=Backup script failed"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;You know everything: whether it's working, how long it takes, when it last executed.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CronMonitor - Monitoring for Your Cron
&lt;/h3&gt;

&lt;p&gt;CronMonitor is a service created with developers in mind who want to sleep peacefully knowing their cron jobs are working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔔 &lt;strong&gt;Smart Alerts&lt;/strong&gt; - Email, SMS, Slack, Discord, Telegram, webhooks&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Execution History&lt;/strong&gt; - Full visibility into what happened&lt;/li&gt;
&lt;li&gt;⏱️ &lt;strong&gt;Execution Time Monitoring&lt;/strong&gt; - Anomaly detection&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Simple Setup&lt;/strong&gt; - One curl in your script&lt;/li&gt;
&lt;li&gt;🌍 &lt;strong&gt;Multi-Timezone Support&lt;/strong&gt; - For distributed infrastructure&lt;/li&gt;
&lt;li&gt;📈 &lt;strong&gt;Dashboard&lt;/strong&gt; - View all tasks in one place&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register a monitor at &lt;a href="https://cronmonitor.app" rel="noopener noreferrer"&gt;cronmonitor.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Receive unique ping URL&lt;/li&gt;
&lt;li&gt;Add ping to your cron:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /backup.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl https://cronmonitor.app/ping/YOUR-UNIQUE-ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Done! Get alerts when backup stops working&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Perfect For:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💼 &lt;strong&gt;SaaS Applications&lt;/strong&gt; - Monitoring background jobs&lt;/li&gt;
&lt;li&gt;🔧 &lt;strong&gt;DevOps Teams&lt;/strong&gt; - Centralized infrastructure monitoring&lt;/li&gt;
&lt;li&gt;🏢 &lt;strong&gt;Agencies&lt;/strong&gt; - Monitoring clients in one place&lt;/li&gt;
&lt;li&gt;👨‍💻 &lt;strong&gt;Indie Hackers&lt;/strong&gt; - Peace of mind at minimal cost&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary: Cron Lives and Thrives
&lt;/h2&gt;

&lt;p&gt;After 50 years, cron remains a fundamental tool in the Unix/Linux world. Its simplicity and reliability mean that despite dozens of alternatives, it remains the first choice for most automation tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to Use Cron:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Simple, predictable tasks&lt;/li&gt;
&lt;li&gt;✅ Resource-constrained environments&lt;/li&gt;
&lt;li&gt;✅ When you need something that "just works"&lt;/li&gt;
&lt;li&gt;✅ One-off scripts and maintenance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to Consider Alternatives:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Complex workflows with dependencies&lt;/li&gt;
&lt;li&gt;❌ High time precision required (&amp;lt;1 min)&lt;/li&gt;
&lt;li&gt;❌ Advanced retry logic needed&lt;/li&gt;
&lt;li&gt;❌ Cloud-native environment requiring full integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Golden Rule:&lt;/strong&gt; Use cron to execute tasks, but ALWAYS add monitoring. This combination of cron's simplicity with certainty that your tasks are working provides the best efficiency-to-maintenance-cost ratio.&lt;/p&gt;

&lt;p&gt;The future isn't about replacing cron, but complementing it with intelligent tools that eliminate its biggest weakness - lack of visibility. Cron + monitoring = winning combination.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want to sleep peacefully knowing your cron jobs are working? Check out &lt;a href="https://cronmonitor.app" rel="noopener noreferrer"&gt;CronMonitor.app&lt;/a&gt; - simple monitoring that alerts you when something goes wrong.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cron</category>
      <category>linux</category>
      <category>devops</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>How to safely run cron jobs in Docker (with monitoring)</title>
      <dc:creator>Łukasz Maśląg</dc:creator>
      <pubDate>Tue, 09 Dec 2025 20:33:43 +0000</pubDate>
      <link>https://dev.to/cronmonitor/how-to-safely-run-cron-jobs-in-docker-with-monitoring-4nkm</link>
      <guid>https://dev.to/cronmonitor/how-to-safely-run-cron-jobs-in-docker-with-monitoring-4nkm</guid>
      <description>&lt;p&gt;``Have you encountered the following issues when running tasks using the system cron in Decoder?:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logs disappear into thin air&lt;/li&gt;
&lt;li&gt;Tasks fail at 3 a.m.&lt;/li&gt;
&lt;li&gt;Environment variables don't work&lt;/li&gt;
&lt;li&gt;Container doesn't shut down properly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Problem with system cron
&lt;/h2&gt;

&lt;p&gt;The cron daemon was designed for traditional Linux servers, not containers:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;dockerfile&lt;/p&gt;

&lt;h1&gt;
  
  
  ❌ Don't do this
&lt;/h1&gt;

&lt;p&gt;FROM ubuntu:22.04&lt;br&gt;
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y cron&lt;br&gt;
COPY mycron /etc/cron.d/mycron&lt;br&gt;
RUN crontab /etc/cron.d/mycron&lt;br&gt;
CMD cron -f&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What goes wrong:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No logs&lt;/strong&gt; - cron writes to syslog, not stdout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bad signals&lt;/strong&gt; - SIGTERM doesn't stop jobs gracefully&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ENV hell&lt;/strong&gt; - cron doesn't inherit Docker ENV variables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PID 1 issues&lt;/strong&gt; - if cron is PID 1, signal handling breaks&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Solution: Supercronic
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/aptible/supercronic" rel="noopener noreferrer"&gt;Supercronic&lt;/a&gt; is cron reimagined for containers:&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;dockerfile&lt;/p&gt;

&lt;h1&gt;
  
  
  ✅ Do this instead
&lt;/h1&gt;

&lt;p&gt;FROM php:8.2-cli&lt;/p&gt;

&lt;p&gt;ENV SUPERCRONIC_URL=&lt;a href="https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64" rel="noopener noreferrer"&gt;https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64&lt;/a&gt; \&lt;br&gt;
    SUPERCRONIC=supercronic-linux-amd64 \&lt;br&gt;
    SUPERCRONIC_SHA1SUM=cd48d45c4b10f3f0bfdd3a57d054cd05ac96812b&lt;/p&gt;

&lt;p&gt;RUN curl -fsSLO "$SUPERCRONIC_URL" \&lt;br&gt;
 &amp;amp;&amp;amp; echo "${SUPERCRONIC_SHA1SUM}  ${SUPERCRONIC}" | sha1sum -c - \&lt;br&gt;
 &amp;amp;&amp;amp; chmod +x "$SUPERCRONIC" \&lt;br&gt;
 &amp;amp;&amp;amp; mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" \&lt;br&gt;
 &amp;amp;&amp;amp; ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic&lt;/p&gt;

&lt;p&gt;COPY crontab /app/crontab&lt;br&gt;
CMD ["supercronic", "/app/crontab"]&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;The syntax in supercronic remains the same as in crontab:&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  /app/crontab
&lt;/h1&gt;

&lt;p&gt;*/15 * * * * php /app/bin/console app:import-data&lt;br&gt;
0 2 * * * /app/scripts/backup.sh&lt;br&gt;
0 */4 * * * php /app/bin/console cache:clear&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Logs go to stdout (&lt;code&gt;docker logs&lt;/code&gt; works!)&lt;/li&gt;
&lt;li&gt;✅ Graceful shutdown on SIGTERM&lt;/li&gt;
&lt;li&gt;✅ Inherits ENV from Docker automatically&lt;/li&gt;
&lt;li&gt;✅ Single process, no daemon complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sample Symfony application running in a container:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Dockerfile:&lt;/strong&gt;&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;dockerfile&lt;br&gt;
FROM php:8.2-cli&lt;/p&gt;

&lt;h1&gt;
  
  
  Install Supercronic
&lt;/h1&gt;

&lt;p&gt;ENV SUPERCRONIC_URL=&lt;a href="https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64" rel="noopener noreferrer"&gt;https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64&lt;/a&gt;&lt;br&gt;
RUN curl -fsSLO "$SUPERCRONIC_URL" \&lt;br&gt;
 &amp;amp;&amp;amp; chmod +x supercronic-linux-amd64 \&lt;br&gt;
 &amp;amp;&amp;amp; mv supercronic-linux-amd64 /usr/local/bin/supercronic \&lt;br&gt;
 &amp;amp;&amp;amp; apt-get update &amp;amp;&amp;amp; apt-get install -y git curl unzip&lt;/p&gt;

&lt;h1&gt;
  
  
  Install Composer
&lt;/h1&gt;

&lt;p&gt;COPY --from=composer:latest /usr/bin/composer /usr/bin/composer&lt;/p&gt;

&lt;h1&gt;
  
  
  Application
&lt;/h1&gt;

&lt;p&gt;COPY . /app&lt;br&gt;
WORKDIR /app&lt;br&gt;
RUN composer install --no-dev --optimize-autoloader&lt;/p&gt;

&lt;p&gt;COPY docker/crontab /app/crontab&lt;br&gt;
CMD ["supercronic", "/app/crontab"]&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker/crontab:&lt;/strong&gt;&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Process message queue
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;* * * * php /app/bin/console messenger:consume async --time-limit=3600&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Send scheduled emails
&lt;/h1&gt;

&lt;p&gt;*/5 * * * * php /app/bin/console app:send-emails&lt;/p&gt;

&lt;h1&gt;
  
  
  Generate daily reports
&lt;/h1&gt;

&lt;p&gt;0 6 * * * php /app/bin/console app:generate-reports&lt;/p&gt;

&lt;h1&gt;
  
  
  Cleanup old data
&lt;/h1&gt;

&lt;p&gt;0 3 * * * php /app/bin/console cache:clear&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker-compose.yml:&lt;/strong&gt;&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;yaml&lt;br&gt;
version: '3.8'&lt;/p&gt;

&lt;p&gt;services:&lt;br&gt;
  app:&lt;br&gt;
    build: .&lt;br&gt;
    # ... your app service&lt;/p&gt;

&lt;p&gt;cron:&lt;br&gt;
    build: .&lt;br&gt;
    container_name: app-cron&lt;br&gt;
    restart: unless-stopped&lt;br&gt;
    env_file: .env&lt;br&gt;
    depends_on:&lt;br&gt;
      - db&lt;br&gt;
      - redis&lt;br&gt;
    networks:&lt;br&gt;
      - app-network&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;h2&gt;
  
  
  Missing Element: Monitoring
&lt;/h2&gt;

&lt;p&gt;Now the cron jobs are working, but how do we find out if and when they failed?&lt;/p&gt;

&lt;p&gt;The job fails at 3 a.m. → We'll probably find out when we need to restore a database backup, and cron hasn't executed a script in two weeks :( → Data loss, angry users, a bad day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution: HTTP pings
&lt;/h3&gt;

&lt;p&gt;I created &lt;a href="https://cronmonitor.app" rel="noopener noreferrer"&gt;CronMonitor.app&lt;/a&gt; specifically for this purpose:&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Add ping after command
&lt;/h1&gt;

&lt;p&gt;*/15 * * * * php /app/your-command.php &amp;amp;&amp;amp; curl -fsS &lt;a href="https://cronmonitor.app/ping/%7Btoken%7D" rel="noopener noreferrer"&gt;https://cronmonitor.app/ping/{token}&lt;/a&gt; &lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8sotvewvje5l6bagn0ji.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8sotvewvje5l6bagn0ji.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you get:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slack/Discord/email alerts when tasks aren't completed correctly&lt;/li&gt;
&lt;li&gt;Dashboard displaying all tasks&lt;/li&gt;
&lt;li&gt;Duration and error tracking&lt;/li&gt;
&lt;li&gt;No agents, no complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example of a complete multi-container solution:
&lt;/h2&gt;

&lt;p&gt;Example of monitoring tasks across multiple containers in a production environment&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;yaml&lt;br&gt;
version: '3.8'&lt;/p&gt;

&lt;p&gt;services:&lt;br&gt;
  # Main application&lt;br&gt;
  app:&lt;br&gt;
    image: myapp:latest&lt;br&gt;
    # ...&lt;/p&gt;

&lt;p&gt;# Cron worker for background jobs&lt;br&gt;
  cron-worker:&lt;br&gt;
    build:&lt;br&gt;
      context: .&lt;br&gt;
      dockerfile: docker/Dockerfile.cron&lt;br&gt;
    container_name: myapp-cron&lt;br&gt;
    restart: unless-stopped&lt;br&gt;
    env_file: .env&lt;br&gt;
    depends_on:&lt;br&gt;
      - db&lt;br&gt;
      - redis&lt;br&gt;
    logging:&lt;br&gt;
      driver: "json-file"&lt;br&gt;
      options:&lt;br&gt;
        max-size: "10m"&lt;br&gt;
        max-file: "3"&lt;br&gt;
    deploy:&lt;br&gt;
      resources:&lt;br&gt;
        limits:&lt;br&gt;
          cpus: '0.5'&lt;br&gt;
          memory: 512M&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker/Dockerfile.cron:&lt;/strong&gt;&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;dockerfile&lt;br&gt;
FROM php:8.2-cli&lt;/p&gt;

&lt;h1&gt;
  
  
  Supercronic
&lt;/h1&gt;

&lt;p&gt;RUN curl -L &lt;a href="https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64" rel="noopener noreferrer"&gt;https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64&lt;/a&gt; \&lt;br&gt;
    -o /usr/local/bin/supercronic &amp;amp;&amp;amp; chmod +x /usr/local/bin/supercronic&lt;/p&gt;

&lt;h1&gt;
  
  
  App dependencies
&lt;/h1&gt;

&lt;p&gt;COPY --from=composer:latest /usr/bin/composer /usr/bin/composer&lt;br&gt;
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y git curl unzip libzip-dev \&lt;br&gt;
    &amp;amp;&amp;amp; docker-php-ext-install pdo_mysql zip&lt;/p&gt;

&lt;h1&gt;
  
  
  Application
&lt;/h1&gt;

&lt;p&gt;COPY composer.* /app/&lt;br&gt;
WORKDIR /app&lt;br&gt;
RUN composer install --no-dev --no-scripts --optimize-autoloader&lt;/p&gt;

&lt;p&gt;COPY . /app&lt;br&gt;
RUN composer dump-autoload --optimize&lt;/p&gt;

&lt;p&gt;COPY docker/crontab /app/crontab&lt;br&gt;
CMD ["supercronic", "-debug", "/app/crontab"]&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker/crontab with monitoring:&lt;/strong&gt;&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Queue processor - critical, monitor closely
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;* * * * curl -fsS &lt;a href="https://cronmonitor.app/ping/%7Btoken%7D" rel="noopener noreferrer"&gt;https://cronmonitor.app/ping/{token}&lt;/a&gt; &amp;amp;&amp;amp; php bin/console messenger:consume --time-limit=3600&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Hourly data sync
&lt;/h1&gt;

&lt;p&gt;0 * * * * curl -fsS &lt;a href="https://cronmonitor.app/ping/%7Btoken%7D" rel="noopener noreferrer"&gt;https://cronmonitor.app/ping/{token}&lt;/a&gt; &amp;amp;&amp;amp; php bin/console app:sync-data || curl -fsS &lt;a href="https://cronmonitor.io/fail/sync-def456" rel="noopener noreferrer"&gt;https://cronmonitor.io/fail/sync-def456&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Daily backup - MUST succeed
&lt;/h1&gt;

&lt;p&gt;0 2 * * * curl -fsS &lt;a href="https://cronmonitor.app/ping/%7Btoken%7D" rel="noopener noreferrer"&gt;https://cronmonitor.app/ping/{token}&lt;/a&gt; &amp;amp;&amp;amp; /app/scripts/backup.sh&lt;/p&gt;

&lt;h1&gt;
  
  
  Weekly cleanup
&lt;/h1&gt;

&lt;p&gt;0 3 * * 0 curl -fsS &lt;a href="https://cronmonitor.app/ping/%7Btoken%7D" rel="noopener noreferrer"&gt;https://cronmonitor.app/ping/{token}&lt;/a&gt; &amp;amp;&amp;amp; php bin/console app:cleanup&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging Tips
&lt;/h2&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  View logs in real-time
&lt;/h1&gt;

&lt;p&gt;docker logs -f myapp-cron&lt;/p&gt;

&lt;h1&gt;
  
  
  Test individual commands
&lt;/h1&gt;

&lt;p&gt;docker exec myapp-cron php bin/console app:your-command&lt;/p&gt;

&lt;h1&gt;
  
  
  Check environment variables
&lt;/h1&gt;

&lt;p&gt;docker exec myapp-cron env | grep DATABASE&lt;/p&gt;

&lt;h1&gt;
  
  
  Manual crontab test
&lt;/h1&gt;

&lt;p&gt;docker exec myapp-cron supercronic -test /app/crontab&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration Checklist
&lt;/h2&gt;

&lt;p&gt;Moving from standard cron to this setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Replace &lt;code&gt;cron&lt;/code&gt; with Supercronic in Dockerfile&lt;/li&gt;
&lt;li&gt;[ ] Move crontab to separate file&lt;/li&gt;
&lt;li&gt;[ ] Test ENV variables are accessible&lt;/li&gt;
&lt;li&gt;[ ] Add monitoring pings to jobs&lt;/li&gt;
&lt;li&gt;[ ] Update docker-compose with health checks&lt;/li&gt;
&lt;li&gt;[ ] Set up resource limits+&lt;/li&gt;
&lt;li&gt;[ ] Test graceful shutdown&lt;/li&gt;
&lt;li&gt;[ ] Document your cron jobs!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;After years of fighting with cron in Docker:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use Supercronic&lt;/strong&gt; - it's designed for containers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor your jobs&lt;/strong&gt; - silent failures are the worst&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep it simple&lt;/strong&gt; - HTTP pings &amp;gt; complex monitoring&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This stack has been running in production for months across multiple projects. Zero silent failures, clear logs, easy debugging.&lt;/p&gt;

&lt;p&gt;Full documentation and examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aptible/supercronic" rel="noopener noreferrer"&gt;Supercronic GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have any questions, leave a comment!&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I created CronMonitor.app after dealing with silent cron failures too many times. It's free for small projects, and I use it for both personal projects and strictly business applications.&lt;em&gt;**What exactly:&lt;/em&gt;*&lt;/li&gt;
&lt;li&gt;Slack/Discord/email alerts when tasks haven't completed correctly&lt;/li&gt;
&lt;li&gt;Dashboard displaying all tasks&lt;/li&gt;
&lt;li&gt;Duration and error tracking&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>monitoring</category>
      <category>cron</category>
      <category>superconic</category>
    </item>
    <item>
      <title>How to Monitor Cron Jobs in 2026: A Complete Guide</title>
      <dc:creator>Łukasz Maśląg</dc:creator>
      <pubDate>Thu, 04 Dec 2025 20:47:37 +0000</pubDate>
      <link>https://dev.to/cronmonitor/how-to-monitor-cron-jobs-in-2026-a-complete-guide-28g9</link>
      <guid>https://dev.to/cronmonitor/how-to-monitor-cron-jobs-in-2026-a-complete-guide-28g9</guid>
      <description>&lt;h1&gt;
  
  
  How to Monitor Cron Jobs in 2026: A Complete Guide
&lt;/h1&gt;

&lt;p&gt;Cron jobs are the silent workhorses of modern applications. They run backups, clean up data, send emails, sync with APIs, and handle countless other critical tasks. But here's the problem: &lt;strong&gt;when they fail, they fail silently&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I learned this the hard way when I discovered a month's worth of database backups had been failing. The cron job was still "running" - it just wasn't doing anything useful. That's when I realized: running a cron job and successfully completing it are two very different things.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Traditional Cron
&lt;/h2&gt;

&lt;p&gt;Traditional cron has zero built-in monitoring. You can log output to a file, sure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /usr/local/bin/backup.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/backup.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have to remember to check the logs&lt;/li&gt;
&lt;li&gt;Logs grow indefinitely (hello disk space issues)&lt;/li&gt;
&lt;li&gt;No alerts when something breaks&lt;/li&gt;
&lt;li&gt;You only notice when you need that backup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cron's job is to run commands on schedule. It's not designed to tell you if those commands actually succeeded.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Need to Monitor
&lt;/h2&gt;

&lt;p&gt;When monitoring cron jobs, we care about several things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Did it run at all?&lt;/strong&gt; (The job might be disabled, the server might be down)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Did it complete successfully?&lt;/strong&gt; (Exit code 0 vs errors)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Did it run on time?&lt;/strong&gt; (Server overload, resource constraints)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How long did it take?&lt;/strong&gt; (Performance degradation over time)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What was the output?&lt;/strong&gt; (Errors, warnings, statistics)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's look at different approaches to solving this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 1: Email Alerts (Basic)
&lt;/h2&gt;

&lt;p&gt;The simplest approach is using cron's built-in email feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;MAILTO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin@example.com
0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /usr/local/bin/backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero setup&lt;/li&gt;
&lt;li&gt;Works out of the box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only notifies on failures (STDERR output)&lt;/li&gt;
&lt;li&gt;Requires mail server configuration&lt;/li&gt;
&lt;li&gt;No success confirmation&lt;/li&gt;
&lt;li&gt;Email overload from multiple jobs&lt;/li&gt;
&lt;li&gt;Can't track history or patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Good for personal projects with 1-2 cron jobs. Not scalable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 2: Log Files + Manual Checks
&lt;/h2&gt;

&lt;p&gt;Slightly better - centralized logging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# backup.sh&lt;/span&gt;

&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/log/backup/&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y-%m-%d&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.log"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] Starting backup..."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;pg_dump mydb &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /backups/db-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d&lt;span class="si"&gt;)&lt;/span&gt;.sql&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] Backup completed successfully"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] ERROR: Backup failed"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full control over logging&lt;/li&gt;
&lt;li&gt;Detailed output&lt;/li&gt;
&lt;li&gt;Historical record&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Still requires manual checking&lt;/li&gt;
&lt;li&gt;No real-time alerts&lt;/li&gt;
&lt;li&gt;Log rotation complexity&lt;/li&gt;
&lt;li&gt;Disk space management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Better, but you'll still miss failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 3: Dead Man's Switch Pattern
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. Instead of monitoring for failures, we monitor for success. If we don't hear from the job, something's wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Basic Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# backup.sh&lt;/span&gt;

&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://cronmonitor.app/ping/your-unique-id"&lt;/span&gt;

&lt;span class="c"&gt;# Run your backup&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;pg_dump mydb &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /backups/db-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d&lt;span class="si"&gt;)&lt;/span&gt;.sql&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# Signal success&lt;/span&gt;
    curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 3 &lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;/success
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c"&gt;# Signal failure&lt;/span&gt;
    curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 3 &lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;/fail
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the monitoring side, you set up an expected schedule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"This job should ping me every day at 2 AM"&lt;/li&gt;
&lt;li&gt;"If I don't hear from it by 2:30 AM, alert me"&lt;/li&gt;
&lt;li&gt;"If it pings /fail, alert me immediately"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Catches ALL failure modes (job disabled, server down, script errors)&lt;/li&gt;
&lt;li&gt;Real-time alerts&lt;/li&gt;
&lt;li&gt;Historical tracking&lt;/li&gt;
&lt;li&gt;Works from anywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires external service&lt;/li&gt;
&lt;li&gt;Dependency on network connectivity&lt;/li&gt;
&lt;li&gt;Potential costs (though many free tiers exist)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Industry standard for production systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 4: Full Monitoring Solution
&lt;/h2&gt;

&lt;p&gt;For enterprise needs, combine monitoring with observability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# backup.sh with full monitoring&lt;/span&gt;

&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://cronmonitor.app/ping/your-unique-id"&lt;/span&gt;

&lt;span class="c"&gt;# Start signal&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 3 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;/start"&lt;/span&gt;

&lt;span class="c"&gt;# Capture start time&lt;/span&gt;
&lt;span class="nv"&gt;START_TIME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Run backup with output capture&lt;/span&gt;
&lt;span class="nv"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;pg_dump mydb &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /backups/db-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d&lt;span class="si"&gt;)&lt;/span&gt;.sql 2&amp;gt;&amp;amp;1&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;EXIT_CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;

&lt;span class="c"&gt;# Calculate duration&lt;/span&gt;
&lt;span class="nv"&gt;END_TIME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;DURATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;END_TIME &lt;span class="o"&gt;-&lt;/span&gt; START_TIME&lt;span class="k"&gt;))&lt;/span&gt;

&lt;span class="c"&gt;# Report results with context&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$EXIT_CODE&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 3 &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"status=success"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"duration=&lt;/span&gt;&lt;span class="nv"&gt;$DURATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"output=&lt;/span&gt;&lt;span class="nv"&gt;$OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 3 &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"status=fail"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"duration=&lt;/span&gt;&lt;span class="nv"&gt;$DURATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"output=&lt;/span&gt;&lt;span class="nv"&gt;$OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$EXIT_CODE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fma60311nm6syspwtxffp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fma60311nm6syspwtxffp.png" alt=" " width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Success/failure tracking&lt;/li&gt;
&lt;li&gt;Execution duration&lt;/li&gt;
&lt;li&gt;Output logs&lt;/li&gt;
&lt;li&gt;Failure context&lt;/li&gt;
&lt;li&gt;Performance trends over time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-World Implementation Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Handle Network Issues
&lt;/h3&gt;

&lt;p&gt;Always add retries and timeouts to your monitoring pings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 3 &lt;span class="nt"&gt;--retry-delay&lt;/span&gt; 5 &lt;span class="nt"&gt;--max-time&lt;/span&gt; 10 &lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;-f&lt;/code&gt; to fail on HTTP errors, &lt;code&gt;-s&lt;/code&gt; for silent mode, &lt;code&gt;-S&lt;/code&gt; to show errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Don't Let Monitoring Break Your Job
&lt;/h3&gt;

&lt;p&gt;Wrap monitoring in a way that doesn't affect your main task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run the actual job&lt;/span&gt;
/usr/local/bin/backup.sh
&lt;span class="nv"&gt;JOB_EXIT_CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;

&lt;span class="c"&gt;# Try to report status, but don't fail if monitoring is down&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 3 &lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# Exit with the job's actual exit code&lt;/span&gt;
&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$JOB_EXIT_CODE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Set Realistic Grace Periods
&lt;/h3&gt;

&lt;p&gt;Jobs don't always run at exactly the same time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server load varies&lt;/li&gt;
&lt;li&gt;Some tasks take longer with more data&lt;/li&gt;
&lt;li&gt;Network latency affects things&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set grace periods accordingly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast jobs (&amp;lt; 1 min): 5-10 minute grace period&lt;/li&gt;
&lt;li&gt;Medium jobs (5-30 min): 15-30 minute grace period&lt;/li&gt;
&lt;li&gt;Long jobs (hours): 1-2 hour grace period&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Monitor the Monitors
&lt;/h3&gt;

&lt;p&gt;What happens if your monitoring service goes down? Have a backup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PRIMARY_MONITOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://cronmonitor.app/ping/abc123"&lt;/span&gt;
&lt;span class="nv"&gt;BACKUP_MONITOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://backup-service.com/ping/xyz789"&lt;/span&gt;

curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 2 &lt;span class="nv"&gt;$PRIMARY_MONITOR&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 2 &lt;span class="nv"&gt;$BACKUP_MONITOR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Use Environment Variables
&lt;/h3&gt;

&lt;p&gt;Don't hardcode monitoring URLs in scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/cron.d/backups&lt;/span&gt;
&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://cronmonitor.app/ping/abc123
0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; user /usr/local/bin/backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# backup.sh&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;'curl -fsS "$MONITOR_URL/fail"'&lt;/span&gt; ERR
    &lt;span class="c"&gt;# Your job here&lt;/span&gt;
    curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;/success"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Timezone Considerations
&lt;/h2&gt;

&lt;p&gt;This is often overlooked but critical. Your server might be in UTC, your team in EST, and your monitoring service in another timezone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Practice:&lt;/strong&gt; Always think in UTC for cron schedules, translate to local time in your monitoring tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Server in UTC, backup at 2 AM EST (7 AM UTC)&lt;/span&gt;
0 7 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /usr/local/bin/backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure your monitoring with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schedule: "Daily at 7:00 UTC" (system time)&lt;/li&gt;
&lt;li&gt;Display: "2:00 AM EST" (human time)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Pitfalls to Avoid
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Not Monitoring Start Time
&lt;/h3&gt;

&lt;p&gt;Only checking if a job completed misses jobs that hang:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# BAD: Only ping at the end&lt;/span&gt;
run_backup
curl &lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;

&lt;span class="c"&gt;# GOOD: Ping start and end&lt;/span&gt;
curl &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;/start"&lt;/span&gt;
run_backup
curl &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;/end"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Ignoring Exit Codes
&lt;/h3&gt;

&lt;p&gt;Your script might "finish" but with errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# BAD: Always reports success&lt;/span&gt;
backup.sh
curl &lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;

&lt;span class="c"&gt;# GOOD: Check exit code&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;backup.sh&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;curl &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;/success"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;curl &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;/fail"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Alert Fatigue
&lt;/h3&gt;

&lt;p&gt;Don't alert on every tiny issue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use grace periods&lt;/li&gt;
&lt;li&gt;Group related alerts&lt;/li&gt;
&lt;li&gt;Set up on-call rotations&lt;/li&gt;
&lt;li&gt;Distinguish critical vs warning&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. No Runbook
&lt;/h3&gt;

&lt;p&gt;When alerts fire at 3 AM, you want answers fast:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# monitoring-config.yaml&lt;/span&gt;
&lt;span class="na"&gt;monitors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Database&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Backup"&lt;/span&gt;
    &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
    &lt;span class="na"&gt;runbook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;1. Check disk space: df -h /backups&lt;/span&gt;
      &lt;span class="s"&gt;2. Check database connectivity: psql -c "\l"&lt;/span&gt;
      &lt;span class="s"&gt;3. Review logs: tail -n 100 /var/log/backup.log&lt;/span&gt;
      &lt;span class="s"&gt;4. Manual backup: /usr/local/bin/backup.sh&lt;/span&gt;
      &lt;span class="s"&gt;5. Escalate to: db-team@company.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Choosing a Monitoring Solution
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Self-Hosted Options
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Healthchecks.io&lt;/strong&gt; (Open Source)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free, self-hosted&lt;/li&gt;
&lt;li&gt;Simple and reliable&lt;/li&gt;
&lt;li&gt;Python/Django based&lt;/li&gt;
&lt;li&gt;Good for small teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cronitor&lt;/strong&gt; (Commercial, has open-source version)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feature-rich&lt;/li&gt;
&lt;li&gt;Beautiful UI&lt;/li&gt;
&lt;li&gt;Higher cost&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SaaS Options
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;My tool - CronMonitor&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dead simple setup&lt;/li&gt;
&lt;li&gt;Timezone-aware&lt;/li&gt;
&lt;li&gt;Generous free tier (10 monitors)&lt;/li&gt;
&lt;li&gt;Built by someone who felt your pain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Others:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cronitor (established, expensive)&lt;/li&gt;
&lt;li&gt;Better Uptime (includes cron monitoring)&lt;/li&gt;
&lt;li&gt;Dead Man's Snitch (simple, focused)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Choosing criteria:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number of jobs you need to monitor&lt;/li&gt;
&lt;li&gt;Budget&lt;/li&gt;
&lt;li&gt;Need for self-hosting&lt;/li&gt;
&lt;li&gt;Integration requirements (Slack, PagerDuty, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Complete Example: Production-Ready Script
&lt;/h2&gt;

&lt;p&gt;Here's a fully instrumented backup script you can adapt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="c"&gt;# Configuration&lt;/span&gt;
&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/backups"&lt;/span&gt;
&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"production"&lt;/span&gt;
&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONITOR_URL&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;RETENTION_DAYS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;30
&lt;span class="nv"&gt;ALERT_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"admin@example.com"&lt;/span&gt;

&lt;span class="c"&gt;# Setup logging&lt;/span&gt;
&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/log/backup/&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y-%m-%d&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.log"&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;1&amp;gt; &lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;2&amp;gt;&amp;amp;1

log&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +&lt;span class="s1"&gt;'%Y-%m-%d %H:%M:%S'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="nv"&gt;$*&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Signal start to monitoring&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 3 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;/start"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; log &lt;span class="s2"&gt;"WARNING: Could not signal start"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;log &lt;span class="s2"&gt;"Starting backup process"&lt;/span&gt;

&lt;span class="c"&gt;# Check prerequisites&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; pg_dump &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log &lt;span class="s2"&gt;"ERROR: pg_dump not found"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log &lt;span class="s2"&gt;"ERROR: Backup directory &lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt; does not exist"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Check disk space (need at least 10GB)&lt;/span&gt;
&lt;span class="nv"&gt;AVAILABLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-BG&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'NR==2 {print $4}'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/G//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AVAILABLE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-lt&lt;/span&gt; 10 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log &lt;span class="s2"&gt;"ERROR: Insufficient disk space. Available: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AVAILABLE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;GB"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Run backup&lt;/span&gt;
&lt;span class="nv"&gt;BACKUP_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;/db-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d-%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.sql.gz"&lt;/span&gt;
&lt;span class="nv"&gt;START_TIME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;

log &lt;span class="s2"&gt;"Creating backup: &lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;pg_dump &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;END_TIME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;DURATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;END_TIME &lt;span class="o"&gt;-&lt;/span&gt; START_TIME&lt;span class="k"&gt;))&lt;/span&gt;
    &lt;span class="nv"&gt;SIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

    log &lt;span class="s2"&gt;"Backup completed successfully in &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DURATION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s, size: &lt;/span&gt;&lt;span class="nv"&gt;$SIZE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="c"&gt;# Verify backup is not empty&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;log &lt;span class="s2"&gt;"ERROR: Backup file is empty"&lt;/span&gt;
        &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;exit &lt;/span&gt;1
    &lt;span class="k"&gt;fi&lt;/span&gt;

    &lt;span class="c"&gt;# Cleanup old backups&lt;/span&gt;
    log &lt;span class="s2"&gt;"Cleaning up backups older than &lt;/span&gt;&lt;span class="nv"&gt;$RETENTION_DAYS&lt;/span&gt;&lt;span class="s2"&gt; days"&lt;/span&gt;
    find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"db-*.sql.gz"&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +&lt;span class="nv"&gt;$RETENTION_DAYS&lt;/span&gt; &lt;span class="nt"&gt;-delete&lt;/span&gt;

    &lt;span class="c"&gt;# Signal success&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 3 &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"status=success"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"duration=&lt;/span&gt;&lt;span class="nv"&gt;$DURATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"size=&lt;/span&gt;&lt;span class="nv"&gt;$SIZE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; log &lt;span class="s2"&gt;"WARNING: Could not signal success"&lt;/span&gt;
    &lt;span class="k"&gt;fi

    &lt;/span&gt;log &lt;span class="s2"&gt;"Backup process completed successfully"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;else
    &lt;/span&gt;log &lt;span class="s2"&gt;"ERROR: Backup failed"&lt;/span&gt;

    &lt;span class="c"&gt;# Signal failure&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 3 &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"status=fail"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"error=pg_dump failed"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONITOR_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; log &lt;span class="s2"&gt;"WARNING: Could not signal failure"&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;

    &lt;span class="c"&gt;# Send email alert&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; mail &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Backup failed. Check logs at &lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
            mail &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"ALERT: Backup Failed"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ALERT_EMAIL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi

    &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Monitoring cron jobs isn't optional for production systems. The question isn't "should we monitor?" but "how should we monitor?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start simple:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add health check pings to your most critical jobs&lt;/li&gt;
&lt;li&gt;Set up alerts for failures&lt;/li&gt;
&lt;li&gt;Track patterns over time&lt;/li&gt;
&lt;li&gt;Iterate and improve&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Remember:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cron jobs fail silently by design&lt;/li&gt;
&lt;li&gt;You need active monitoring, not passive logging&lt;/li&gt;
&lt;li&gt;Set realistic grace periods&lt;/li&gt;
&lt;li&gt;Monitor the monitors&lt;/li&gt;
&lt;li&gt;Document your runbooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The peace of mind from knowing your backups actually ran? Worth every minute of setup time.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's your cron monitoring strategy?&lt;/strong&gt; Drop a comment - I'd love to hear how others are solving this problem!&lt;/p&gt;

&lt;p&gt;If you're looking for a simple solution to get started, I built &lt;a href="https://cronmonitor.app" rel="noopener noreferrer"&gt;CronMonitor&lt;/a&gt; specifically for this. Free tier includes 10 monitors, no credit card needed.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. - If you found this helpful, follow me for more DevOps and SaaS content!&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Additional Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://crontab.guru" rel="noopener noreferrer"&gt;Cron Expression Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bertvv.github.io/cheat-sheets/Bash.html" rel="noopener noreferrer"&gt;Bash Error Handling Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://12factor.net/logs" rel="noopener noreferrer"&gt;The Twelve-Factor App - Logs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Cover image: A terminal window showing cron job output with monitoring indicators&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cron</category>
      <category>cronjob</category>
      <category>monitoring</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
