{"id":1341,"date":"2026-10-08T23:27:45","date_gmt":"2026-10-08T23:27:45","guid":{"rendered":"https:\/\/virtualserversvps.com\/blog\/?p=1341"},"modified":"2026-10-08T23:27:45","modified_gmt":"2026-10-08T23:27:45","slug":"auditing-cron-job-failures-email-alerting-small-vps","status":"publish","type":"post","link":"https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/","title":{"rendered":"Auditing Cron Job Failures and Email Alerting on a Small VPS"},"content":{"rendered":"<div style=\"padding:20px\">\n<p class=\"wp-block-paragraph\">Cron is the most common scheduler on a VPS and the easiest to forget. A typo in a path, an expired token, a full disk &mdash; and a backup or certificate renewal silently stops running, sometimes for months, because nobody reads the mail that cron occasionally tries to send. The fix is not more cron entries; it is making every job report its outcome and architecting so that a failure is loud. This guide audits what is actually scheduled, makes jobs fail visibly, and wires reliable alerting that survives a broken local mailer.<\/p>\n<p class=\"wp-block-paragraph\">Alerting is only as good as the box generating it, so the underlying instance must be sized to run your monitoring without contention. If jobs are crawling because the host is oversold, <a href=\"https:\/\/virtualserversvps.com\/\">compare VPS plans<\/a> and check the steal and disk figures before you blame your scripts &mdash; and the <a href=\"https:\/\/virtualserversvps.com\/\">plan comparison<\/a> helps you confirm the RAM headroom an alerting agent needs.<\/p>\n<h2 class=\"wp-block-heading\">Step 1: Inventory Every Scheduled Job<\/h2>\n<p class=\"wp-block-paragraph\">There is more than one crontab, and the one you see with <code>crontab -l<\/code> is only your own. Enumerate them all:<\/p>\n<pre class=\"wp-block-code\"><code>for u in $(cut -d: -f1 \/etc\/passwd); do\n  echo \"== $u ==\"; crontab -l -u \"$u\" 2&gt;\/dev\/null\ndone\nls -l \/etc\/cron.d \/etc\/cron.daily \/etc\/cron.hourly\nsystemctl list-timers --all<\/code><\/pre>\n<p class=\"wp-block-paragraph\">The last line matters because a growing number of packaged jobs run as systemd timers, not cron &mdash; if you only audit crontabs you will miss half the schedule. Record owner, command, schedule and expected runtime for each entry; a job with no owner is a job nobody will notice breaking.<\/p>\n<h2 class=\"wp-block-heading\">Step 2: Make Cron Fail Loudly<\/h2>\n<p class=\"wp-block-paragraph\">Cron mails a job&rsquo;s output to the owner only if the local MTA is configured &mdash; and on a minimal VPS it usually is not, so output is discarded. Two robust patterns avoid relying on local mail entirely.<\/p>\n<p class=\"wp-block-paragraph\"><b>Pattern A: wrap every job and check the exit code.<\/b><\/p>\n<pre class=\"wp-block-code\"><code># \/usr\/local\/bin\/runjob.sh\n#!\/bin\/bash\nname=\"$1\"; shift\nstart=$(date +%s)\nif \"$@\"; then\n  echo \"$(date -Is) OK $name\" &gt;&gt; \/var\/log\/jobs.log\nelse\n  rc=$?\n  echo \"$(date -Is) FAIL($rc) $name\" &gt;&gt; \/var\/log\/jobs.log\n  \/usr\/local\/bin\/alert.sh \"$name failed rc=$rc\"\nfi<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Cron entry: <code>0 3 * * * \/usr\/local\/bin\/runjob.sh backup \/usr\/local\/bin\/backup.sh<\/code>. Every job now writes a one-line verdict to <code>\/var\/log\/jobs.log<\/code>, which is far easier to audit than scattered mail.<\/p>\n<p class=\"wp-block-paragraph\"><b>Pattern B: require explicit success.<\/b> A job that prints nothing on success and logs only errors fails silently if it never runs at all. Better to emit a heartbeat: each successful run appends its timestamp, and a separate watchdog alerts if the newest heartbeat is older than 2&times; the schedule. That catches jobs that crash before printing anything &mdash; the exact failure the naive approach misses.<\/p>\n<h2 class=\"wp-block-heading\">Step 3: A Watchdog for Dead Jobs<\/h2>\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n# alert if backup heartbeat is older than 26 hours\nlast=$(grep 'OK backup' \/var\/log\/jobs.log | tail -1 | awk '{print $1}')\nage=$(( $(date +%s) - $(date -d \"$last\" +%s) ))\n[ \"$age\" -gt 93600 ] &amp;&amp; \/usr\/local\/bin\/alert.sh \"backup heartbeat stale: $((age\/3600))h\"<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Run this watchdog from a <em>different<\/em> scheduler context than the jobs themselves &mdash; if both live in the same crontab and cron dies, the watchdog dies with it. A systemd timer with <code>OnCalendar=*-*-* 06:00:00<\/code> and <code>Persistent=true<\/code> is a good host, because Persistent replays a missed run after downtime.<\/p>\n<h2 class=\"wp-block-heading\">Step 4: Alerting That Does Not Depend on Local Mail<\/h2>\n<p class=\"wp-block-paragraph\">Local sendmail frequently breaks or is blocked by providers, so send alerts straight to an HTTP endpoint where possible. A minimal <code>alert.sh<\/code> using curl to a webhook or a transactional-mail API:<\/p>\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\nmsg=\"[$(hostname)] $1\"\ncurl -fsS -m 10 -X POST https:\/\/hooks.example.com\/vps   -H 'Content-Type: application\/json'   -d \"{\\\"text\\\":\\\"$msg\\\"}\" ||   logger -t alert \"DELIVERY FAILED: $msg\"<\/code><\/pre>\n<p class=\"wp-block-paragraph\">The <code>|| logger<\/code> fallback is deliberate: if the network is down, the alert is at least captured in the journal instead of vanishing. Test it by invoking <code>alert.sh \"test\"<\/code> manually and confirming delivery before trusting it with real failures.<\/p>\n<h2 class=\"wp-block-heading\">Step 5: Watch for Duplicate and Overlapping Runs<\/h2>\n<p class=\"wp-block-paragraph\">A long-running job that overlaps its next invocation can corrupt data or hammer a database. Guard with <code>flock<\/code>:<\/p>\n<pre class=\"wp-block-code\"><code>0 * * * * \/usr\/bin\/flock -n \/var\/lock\/mysync.lock \/usr\/local\/bin\/sync.sh<\/code><\/pre>\n<p class=\"wp-block-paragraph\"><code>-n<\/code> makes the second invocation exit immediately if the first still holds the lock, which is exactly the behaviour you want for backups and syncs.<\/p>\n<h2 class=\"wp-block-heading\">Step 6: Log Rotation and Retention for Job Output<\/h2>\n<p class=\"wp-block-paragraph\">An append-only <code>\/var\/log\/jobs.log<\/code> grows without bound on a busy host. Rotation keeps it useful and caps disk use, and because job verdicts are one line each, even a year of history is tiny:<\/p>\n<pre class=\"wp-block-code\"><code># \/etc\/logrotate.d\/jobs\n\/var\/log\/jobs.log {\n    weekly\n    rotate 12\n    compress\n    missingok\n    notifempty\n    create 0640 root adm\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Keep enough history to spot a job that started failing two months ago but was never noticed. Combine the log with the heartbeat watchdog and a weekly grep for <code>FAIL<\/code>, and you have both a real-time alert and a queryable record.<\/p>\n<h2 class=\"wp-block-heading\">Make the Audit Routine<\/h2>\n<p class=\"wp-block-paragraph\">Once a month, read the last few lines of <code>\/var\/log\/jobs.log<\/code>, confirm every job has a recent <code>OK<\/code>, and check that <code>systemctl list-timers<\/code> shows no failed units. A quick grep turns the log into a report:<\/p>\n<pre class=\"wp-block-code\"><code>grep FAIL \/var\/log\/jobs.log | tail -20\nawk '$2 ~ \/^OK\/ {print $3}' \/var\/log\/jobs.log | sort -u<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Combined with heartbeat watchdogs and webhook alerting, this turns cron from a silent liability into a monitored, auditable schedule &mdash; the difference between discovering a broken backup today and discovering it when you need a restore.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Cron is the most common scheduler on a VPS and the easiest to forget. A typo in a path, an expired token, a full disk &mdash; and a backup or&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":2,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1341","post","type-post","status-publish","format-standard","hentry","category-vps-guides-tutorials"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.1 (Yoast SEO v26.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Auditing Cron Job Failures and Email Alerting on a Small VPS - Virtual Servers VPS Blog<\/title>\n<meta name=\"description\" content=\"Cron is the most common scheduler on a VPS and the easiest to forget. A typo in a path, an expired token, a full disk &mdash; and a backup or certificate\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Auditing Cron Job Failures and Email Alerting on a Small VPS\" \/>\n<meta property=\"og:description\" content=\"Auditing Cron Job Failures and Email Alerting on a Small VPS\" \/>\n<meta property=\"og:url\" content=\"https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/\" \/>\n<meta property=\"og:site_name\" content=\"Virtual Servers VPS Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-08T23:27:45+00:00\" \/>\n<meta name=\"author\" content=\"Virtual-Servers-Vps-Editor\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Virtual-Servers-Vps-Editor\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/\",\"name\":\"Auditing Cron Job Failures and Email Alerting on a Small VPS - Virtual Servers VPS Blog\",\"isPartOf\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\"},\"datePublished\":\"2026-10-08T23:27:45+00:00\",\"author\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\"},\"description\":\"Cron is the most common scheduler on a VPS and the easiest to forget. A typo in a path, an expired token, a full disk &mdash; and a backup or certificate\",\"breadcrumb\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/virtualserversvps.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Auditing Cron Job Failures and Email Alerting on a Small VPS\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/\",\"name\":\"Virtual Servers VPS Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\",\"name\":\"Virtual-Servers-Vps-Editor\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"caption\":\"Virtual-Servers-Vps-Editor\"},\"sameAs\":[\"https:\/\/virtualserversvps.com\/blog\"],\"url\":\"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Auditing Cron Job Failures and Email Alerting on a Small VPS - Virtual Servers VPS Blog","description":"Cron is the most common scheduler on a VPS and the easiest to forget. A typo in a path, an expired token, a full disk &mdash; and a backup or certificate","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/","og_locale":"en_US","og_type":"article","og_title":"Auditing Cron Job Failures and Email Alerting on a Small VPS","og_description":"Auditing Cron Job Failures and Email Alerting on a Small VPS","og_url":"https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/","og_site_name":"Virtual Servers VPS Blog","article_published_time":"2026-10-08T23:27:45+00:00","author":"Virtual-Servers-Vps-Editor","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Virtual-Servers-Vps-Editor","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/","url":"https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/","name":"Auditing Cron Job Failures and Email Alerting on a Small VPS - Virtual Servers VPS Blog","isPartOf":{"@id":"https:\/\/virtualserversvps.com\/blog\/#website"},"datePublished":"2026-10-08T23:27:45+00:00","author":{"@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0"},"description":"Cron is the most common scheduler on a VPS and the easiest to forget. A typo in a path, an expired token, a full disk &mdash; and a backup or certificate","breadcrumb":{"@id":"https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/virtualserversvps.com\/blog\/auditing-cron-job-failures-email-alerting-small-vps\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/virtualserversvps.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Auditing Cron Job Failures and Email Alerting on a Small VPS"}]},{"@type":"WebSite","@id":"https:\/\/virtualserversvps.com\/blog\/#website","url":"https:\/\/virtualserversvps.com\/blog\/","name":"Virtual Servers VPS Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0","name":"Virtual-Servers-Vps-Editor","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","caption":"Virtual-Servers-Vps-Editor"},"sameAs":["https:\/\/virtualserversvps.com\/blog"],"url":"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/"}]}},"_links":{"self":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1341","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/comments?post=1341"}],"version-history":[{"count":1,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1341\/revisions"}],"predecessor-version":[{"id":1342,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1341\/revisions\/1342"}],"wp:attachment":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/media?parent=1341"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/categories?post=1341"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/tags?post=1341"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}