<?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: delay.run</title>
    <description>The latest articles on DEV Community by delay.run (@delayrun).</description>
    <link>https://dev.to/delayrun</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4210417%2F3da89f80-6390-415e-bca5-65d64a57235d.png</url>
      <title>DEV Community: delay.run</title>
      <link>https://dev.to/delayrun</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/delayrun"/>
    <language>en</language>
    <item>
      <title>Cron job not running? A step-by-step checklist (including “it works when I run it manually”)</title>
      <dc:creator>delay.run</dc:creator>
      <pubDate>Tue, 27 Oct 2026 09:20:56 +0000</pubDate>
      <link>https://dev.to/delayrun/cron-job-not-running-a-step-by-step-checklist-including-it-works-when-i-run-it-manually-1nb6</link>
      <guid>https://dev.to/delayrun/cron-job-not-running-a-step-by-step-checklist-including-it-works-when-i-run-it-manually-1nb6</guid>
      <description>&lt;p&gt;You added a line to your crontab, waited, and nothing happened. Or the script works perfectly when you run it in your terminal, but cron never seems to run it. This is one of the most common Linux problems, and it nearly always comes down to a short list of causes. Work through them in this order and you will find it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Is the cron daemon actually running?
&lt;/h2&gt;

&lt;p&gt;Start with the obvious. On Debian and Ubuntu the service is called &lt;code&gt;cron&lt;/code&gt;; on RHEL, Fedora, Rocky and Alma it is &lt;code&gt;crond&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status cron      &lt;span class="c"&gt;# Debian / Ubuntu&lt;/span&gt;
systemctl status crond     &lt;span class="c"&gt;# RHEL family&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it is inactive, start and enable it:&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="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; cron
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside Docker containers there is usually no cron daemon at all unless you install and start one yourself. A crontab file in an image does nothing on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Did cron try to run the job?
&lt;/h2&gt;

&lt;p&gt;Cron logs every job it starts. Check the log before you change anything else, because it tells you whether the problem is the schedule or the command.&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="nb"&gt;grep &lt;/span&gt;CRON /var/log/syslog          &lt;span class="c"&gt;# Debian / Ubuntu&lt;/span&gt;
&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/cron         &lt;span class="c"&gt;# RHEL family&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; cron &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s2"&gt;"1 hour ago"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No line for your job&lt;/strong&gt; at the expected time: the schedule or the crontab file is wrong (steps 3 and 4).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A line saying &lt;code&gt;CMD (...)&lt;/code&gt;&lt;/strong&gt; but nothing happened: cron ran it and the command failed (steps 5 to 8).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Check the schedule syntax
&lt;/h2&gt;

&lt;p&gt;A standard crontab line has five time fields, then the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# m   h   dom mon dow   command
*/5   *   *   *   *     /home/app/bin/sync.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common mistakes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Six fields. Standard cron has no seconds field. &lt;code&gt;0 */5 * * * *&lt;/code&gt; is valid in Spring or Quartz, but in crontab the sixth value becomes part of the command.&lt;/li&gt;
&lt;li&gt;Day-of-month and day-of-week both set. &lt;code&gt;0 9 1 * 1&lt;/code&gt; runs on the 1st of the month &lt;strong&gt;and&lt;/strong&gt; on every Monday, not only on Mondays that fall on the 1st.&lt;/li&gt;
&lt;li&gt;Expecting &lt;code&gt;*/7&lt;/code&gt; in the minute field to mean "every 7 minutes forever". It restarts every hour, so you get :00, :07 … :56, then :00 again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paste the expression into a cron explainer if you are unsure, and confirm it says what you think.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Check which crontab you edited
&lt;/h2&gt;

&lt;p&gt;There are several places cron reads from, and they use different formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;crontab -e&lt;/code&gt; edits &lt;strong&gt;your user's&lt;/strong&gt; crontab. No user column.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/etc/crontab&lt;/code&gt; and files in &lt;code&gt;/etc/cron.d/&lt;/code&gt; have an &lt;strong&gt;extra user column&lt;/strong&gt; after the five time fields: &lt;code&gt;0 3 * * * root /usr/local/bin/backup.sh&lt;/code&gt;. Leave it out and the job silently fails.&lt;/li&gt;
&lt;li&gt;Files in &lt;code&gt;/etc/cron.d/&lt;/code&gt; are ignored if the file name contains a dot (on Debian-based systems), so &lt;code&gt;backup.cron&lt;/code&gt; is skipped while &lt;code&gt;backup&lt;/code&gt; works. They also need to be owned by root and not group- or world-writable.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sudo crontab -e&lt;/code&gt; edits &lt;strong&gt;root's&lt;/strong&gt; crontab, which is a different file from yours. Run &lt;code&gt;crontab -l&lt;/code&gt; and &lt;code&gt;sudo crontab -l&lt;/code&gt; to see both.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also make sure the file ends with a newline. Some cron implementations ignore the last line if it doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. “It runs manually but not in cron”: the environment
&lt;/h2&gt;

&lt;p&gt;This is the big one. When you run a script in your terminal, you have your shell profile, your &lt;code&gt;PATH&lt;/code&gt;, your virtualenv and your current directory. Cron has almost none of that. Its &lt;code&gt;PATH&lt;/code&gt; is typically just &lt;code&gt;/usr/bin:/bin&lt;/code&gt;, its shell is &lt;code&gt;/bin/sh&lt;/code&gt;, and it starts in your home directory.&lt;/p&gt;

&lt;p&gt;So a script that calls &lt;code&gt;node&lt;/code&gt;, &lt;code&gt;python3&lt;/code&gt; from a virtualenv, &lt;code&gt;aws&lt;/code&gt;, &lt;code&gt;docker&lt;/code&gt; or anything in &lt;code&gt;/usr/local/bin&lt;/code&gt; may simply not find the binary.&lt;/p&gt;

&lt;p&gt;Fixes, in order of preference:&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;# Use absolute paths everywhere&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;/5 &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/local/bin/node /home/app/jobs/sync.js

&lt;span class="c"&gt;# Or set PATH at the top of the crontab&lt;/span&gt;
&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the script needs environment variables (API keys, &lt;code&gt;DATABASE_URL&lt;/code&gt;), cron will not load them from &lt;code&gt;.bashrc&lt;/code&gt;. Source a file explicitly or set them in the script.&lt;/p&gt;

&lt;p&gt;To reproduce cron's environment in your terminal and see the real error:&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="nb"&gt;env&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nv"&gt;SHELL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bin/sh &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin:/bin &lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; /bin/sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'/home/app/bin/sync.sh'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Relative paths and the working directory
&lt;/h2&gt;

&lt;p&gt;A script that reads &lt;code&gt;./config.yml&lt;/code&gt; or writes &lt;code&gt;logs/out.txt&lt;/code&gt; works from the project folder but not from cron, which starts in &lt;code&gt;$HOME&lt;/code&gt;. Either &lt;code&gt;cd&lt;/code&gt; first or use absolute paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&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; &lt;span class="nb"&gt;cd&lt;/span&gt; /home/app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./bin/report.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Percent signs and permissions
&lt;/h2&gt;

&lt;p&gt;In a crontab line, &lt;code&gt;%&lt;/code&gt; means "newline" and everything after the first one is sent to the command as standard input. This breaks &lt;code&gt;date +%Y-%m-%d&lt;/code&gt; in a surprising way. Escape it as &lt;code&gt;\%&lt;/code&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; /usr/bin/tar czf /backups/site-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +&lt;span class="se"&gt;\%&lt;/span&gt;F&lt;span class="si"&gt;)&lt;/span&gt;.tgz /var/www
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check the script itself: is it executable (&lt;code&gt;chmod +x&lt;/code&gt;), does it start with a correct shebang (&lt;code&gt;#!/usr/bin/env bash&lt;/code&gt;), and can the cron user read every file it touches?&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Capture the output
&lt;/h2&gt;

&lt;p&gt;By default, cron emails output to the local user, which on most servers means it disappears. Redirect it to a file so you can see what happened:&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;/5 &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; /home/app/bin/sync.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /home/app/logs/sync.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;2&amp;gt;&amp;amp;1&lt;/code&gt; matters: most errors go to standard error. If you have a working mail setup, &lt;code&gt;MAILTO=you@example.com&lt;/code&gt; at the top of the crontab sends output by email instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Time zone and reboot surprises
&lt;/h2&gt;

&lt;p&gt;Cron uses the server's time zone. A job set for &lt;code&gt;0 9 * * *&lt;/code&gt; on a server in UTC runs at 9:00 UTC, not 9:00 where you are. Check with &lt;code&gt;timedatectl&lt;/code&gt;. Some cron versions support &lt;code&gt;CRON_TZ=Europe/Berlin&lt;/code&gt; at the top of the file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@reboot&lt;/code&gt; jobs run when the cron daemon starts, which can be before the network or your database is ready. Add a short wait or a retry loop in the script.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem: cron fails silently
&lt;/h2&gt;

&lt;p&gt;Once your job works, there is a second issue: the next time it breaks, nothing will tell you. A crontab that stops gives no output and no error. The usual ways to cover that are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log to a file &lt;strong&gt;and&lt;/strong&gt; have something watch the file or the job's last run time.&lt;/li&gt;
&lt;li&gt;Use a "dead man's switch" style monitor that alerts when a job fails to check in.&lt;/li&gt;
&lt;li&gt;Move the schedule off the server entirely and have a hosted scheduler call an HTTP endpoint in your app, so retries, run history and alerts live in one place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you go the HTTP route, &lt;a href="https://delay.run" rel="noopener noreferrer"&gt;delay.run&lt;/a&gt; is one option: it calls your URL on a cron schedule, logs each response and alerts you by email, Slack or Discord when runs keep failing.&lt;/p&gt;

&lt;p&gt;Whichever you choose, the checklist above will get the job running. Making sure you hear about it next time it stops is what saves you the Monday-morning surprise.&lt;/p&gt;

</description>
      <category>cron</category>
      <category>linux</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
