agentsclimarketplace

Linux cron manager

Skill chfle/lehnert-claude-skills/skills/linux-cron-manager

Use when user wants to schedule a task with cron, write a crontab entry, create a /etc/cron.d/ file, set up a cron job with locking and logging, convert a cron job to a systemd timer, debug a cron job that isn't running, or understand cron syntax and environment behavior.From its SKILL.md

Install
npx -y skills add chfle/lehnert-claude-skills --skill linux-cron-manager

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

11.7 KB, as published. Nobody here has run it

Linux Cron Manager

Overview

Generates production-ready cron entries and /etc/cron.d/ files with proper environment setup, flock-based locking to prevent overlapping runs, structured logging, and failure alerting. Includes a full cron syntax reference and a conversion path to systemd timers for complex scheduling needs.

Language: Respond in the user's language. Cron files and scripts in English.


When to Use

  • User wants to schedule any recurring task
  • User wants a cron job that logs and alerts on failure
  • User wants to prevent a cron job from running twice at once
  • User wants to convert a cron job to a systemd timer
  • User asks "my cron job isn't running" — debugging
  • User wants to understand cron syntax or cron environment behavior

When NOT to Use

  • User needs complex dependencies between jobs, socket activation, or resource limits → linux-systemd-manager
  • User wants to write the script that cron will run → linux-shell-scriptor

Step 1 — Understand the Request

User saysOutput
"run script X every Y"/etc/cron.d/ entry + wrapper with locking + logging
"crontab entry for..."Direct crontab line with explanation
"my cron job isn't running"Debugging checklist
"convert cron to systemd timer"Equivalent .service + .timer pair
"cron syntax for..."Explanation + validated expression

Cron Syntax Reference

⚠️ Timezone: Cron uses the system timezone (from /etc/timezone). To override per-job, add TZ=America/New_York at the top of the /etc/cron.d/ file — this affects all entries in that file only.

┌──────────── minute       (0–59)
│ ┌────────── hour         (0–23)
│ │ ┌──────── day of month (1–31)
│ │ │ ┌────── month        (1–12 or JAN–DEC)
│ │ │ │ ┌──── day of week  (0–7, 0 and 7 = Sunday, or SUN–SAT)
│ │ │ │ │
* * * * *  user  command

Common expressions:

ScheduleExpression
Every minute* * * * *
Every 5 minutes*/5 * * * *
Every hour at :000 * * * *
Every hour at :3030 * * * *
Daily at 2 AM0 2 * * *
Daily at 2:30 AM30 2 * * *
Every Monday at 3 AM0 3 * * 1
Weekdays at 9 AM0 9 * * 1-5
1st of every month0 0 1 * *
Every 15 minutes*/15 * * * *
Twice daily (6 AM + 6 PM)0 6,18 * * *
Every Sunday at midnight0 0 * * 0

Validate any expression at: https://crontab.guru


Production Cron Entry Template

Never write a bare cron entry. Always wrap with locking, logging, and error handling.

/etc/cron.d/ file format (preferred — survives user crontab edits)

/etc/cron.d/myapp-task:

# myapp-task — describe what this does
# Runs: daily at 2 AM
# Logs: /var/log/cron/myapp-task.log
# Owner: appuser

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=""
HOME=/home/appuser   # cron doesn't source profile — set HOME explicitly to avoid "~" surprises

0 2 * * *  appuser  /usr/bin/flock -n /var/lock/myapp-task.lock /opt/myapp/tasks/run-task.sh

Wrapper script (/opt/myapp/tasks/run-task.sh):

Always generate the wrapper script alongside the cron entry:

#!/usr/bin/env bash
set -euo pipefail

# ── Config ──────────────────────────────────────────────────
SCRIPT_NAME="myapp-task"
LOG_DIR="/var/log/cron"
LOG_FILE="${LOG_DIR}/${SCRIPT_NAME}.log"
LOCK_FILE="/var/lock/${SCRIPT_NAME}.lock"
MAX_LOG_SIZE_MB=50

# ── Logging ──────────────────────────────────────────────────
mkdir -p "$LOG_DIR"
log()   { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO]  $*" | tee -a "$LOG_FILE"; }
warn()  { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARN]  $*" | tee -a "$LOG_FILE" >&2; }
error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" | tee -a "$LOG_FILE" >&2; }

# ── Log rotation ─────────────────────────────────────────────
if [[ -f "$LOG_FILE" ]]; then
  LOG_SIZE_MB=$(du -m "$LOG_FILE" | cut -f1)
  if [[ "$LOG_SIZE_MB" -ge "$MAX_LOG_SIZE_MB" ]]; then
    ARCHIVED_LOG="${LOG_FILE}.$(date +%Y%m%d_%H%M%S)"
    mv "$LOG_FILE" "$ARCHIVED_LOG"
    gzip "$ARCHIVED_LOG"
  fi
fi

# ── Lock — prevent concurrent runs ───────────────────────────
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
  warn "Another instance is already running. Exiting."
  exit 0
fi

# ── Trap — cleanup on exit ───────────────────────────────────
cleanup() {
  local exit_code=$?
  if [[ $exit_code -ne 0 ]]; then
    error "Task failed with exit code $exit_code"
    # Optional: send alert
    # echo "Cron job $SCRIPT_NAME failed" | mail -s "CRON FAILURE: $SCRIPT_NAME" [email protected]
  fi
  flock -u 9
}
trap cleanup EXIT

# ── Main ─────────────────────────────────────────────────────
log "=== Starting $SCRIPT_NAME ==="

# --- YOUR TASK CODE HERE ---
# Example:
# log "Running backup..."
# /opt/backup/backup.sh
# log "Backup complete"
# ---------------------------

log "=== $SCRIPT_NAME complete ==="

Email / Alert on Failure

Add to the cron file to capture output and email on non-zero exit:

# Option 1: Send all output to email (requires working MTA)
MAILTO="[email protected]"
0 2 * * *  appuser  /opt/myapp/tasks/run-task.sh

# Option 2: Only email on failure using a wrapper
0 2 * * *  appuser  /opt/myapp/tasks/run-task.sh || echo "myapp-task failed" | mail -s "CRON FAILURE" [email protected]

# Option 3: Log to file and send to a webhook (Slack/Discord) on failure
0 2 * * *  appuser  /opt/myapp/tasks/run-task.sh 2>&1 | tee -a /var/log/cron/myapp.log | \
  grep -i "error\|fail" | while read line; do \
  curl -s -X POST -H 'Content-type: application/json' \
  --data "{\"text\":\"Cron failure: $line\"}" "${SLACK_WEBHOOK_URL}"; done

Environment Rules

Cron runs in a minimal environment — this is the #1 cause of cron jobs that "work manually but not in cron."

Always include in /etc/cron.d/ files:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

Common environment pitfalls:

ProblemCauseFix
Command not foundPATH is minimal in cronUse full path: /usr/bin/python3
Script works manually, not in cronMissing env varsAdd source /etc/environment or export each var at top of wrapper script
App-specific env vars missing.env file not loadedAdd set -a; source /opt/myapp/.env; set +a at top of wrapper script
Python/Node virtualenv not activeNo shell initSource virtualenv explicitly: source /opt/venv/bin/activate
HOME not setCron doesn't source profileAdd HOME=/home/appuser to cron file
No display/dbusX11/dbus not availableNot suitable for GUI apps; use headless mode

Debugging Checklist

When a cron job isn't running or is failing silently:

# 1. Verify cron daemon is running
systemctl status cron    # Debian/Ubuntu
systemctl status crond   # RHEL/Rocky

# 2. Check cron logs
grep CRON /var/log/syslog | tail -30          # Debian/Ubuntu
journalctl -u cron --since "1 hour ago"       # systemd systems
grep cron /var/log/messages | tail -30        # RHEL/Rocky

# 3. Check the cron entry is valid
crontab -l -u appuser                         # list user crontab
ls -la /etc/cron.d/                           # list system cron jobs

# 4. Test the command manually as the cron user
sudo -u appuser /path/to/script.sh

# 5. Test with same minimal environment as cron
sudo -u appuser env -i HOME=/home/appuser \
  PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin \
  /path/to/script.sh

# 6. Verify file permissions
ls -la /path/to/script.sh        # must be executable
stat /etc/cron.d/myfile          # must be owned by root, mode 644

# 7. Check for syntax errors in /etc/cron.d/ files
# File must NOT have a .sh extension — cron ignores them
# File must NOT be world-writable

The #1 reason cron jobs "silently do nothing": output (stdout + stderr) is discarded unless captured. Always write logs inside the wrapper script using the log() function above, or add >> /var/log/cron/myapp.log 2>&1 to the cron entry.

Common reasons cron jobs silently do nothing:

IssueCheck
Script not executablechmod +x script.sh
/etc/cron.d/ file has .sh extensionRename to no extension
/etc/cron.d/ file is world-writablechmod 644
Wrong user in cron entryVerify user exists: id appuser
Script path wrongUse absolute paths only in cron
Cron daemon not reloadedsystemctl reload cron after adding new file
Lock file stuck from previous crashDelete /var/lock/scriptname.lock

Cron → Systemd Timer Conversion

When to switch to systemd timers instead of cron:

Use cron whenUse systemd timer whenUse Anacron when
Simple schedule, no dependenciesNeed After= dependenciesSystem isn't always on (laptop, desktop)
Script already works standaloneNeed resource limits (MemoryMax, CPUQuota)Daily/weekly tasks that must run even after downtime
Quick setup neededNeed detailed logging via journalctlNo systemd available and Persistent=true is needed
No systemd available (Alpine, containers)Need Persistent=true for missed runs—

Anacron (/etc/anacrontab) — runs jobs that were missed while the system was off:

# period  delay  job-id         command
1         5      daily-backup   /opt/backup/backup.sh
7         10     weekly-report  /opt/reports/weekly.sh

Install: apt install anacron / dnf install cronie-anacron

Conversion example:

Cron entry:

0 2 * * *  appuser  /opt/myapp/backup.sh

Equivalent systemd timer — see /linux-systemd-manager for full output.


Output Format

Write files to ./cron/ in the current working directory:

cron/
  myapp-task          ← drop into /etc/cron.d/ (no extension!)
  run-task.sh         ← wrapper script with locking + logging

Then print ONLY:

✅ Cron job written to ./cron/

▶ Install:
  # ⚠️  /etc/cron.d files MUST be owned by root with mode 644.
  #     Cron silently ignores files with wrong ownership or permissions.
  sudo cp cron/myapp-task /etc/cron.d/myapp-task
  sudo chmod 644 /etc/cron.d/myapp-task
  sudo chown root:root /etc/cron.d/myapp-task
  sudo cp cron/run-task.sh /opt/myapp/tasks/run-task.sh
  sudo chmod 755 /opt/myapp/tasks/run-task.sh
  sudo chown appuser:appuser /opt/myapp/tasks/run-task.sh
  sudo systemctl reload cron          # tell cron to pick up the new /etc/cron.d/ file

▶ Verify cron picked it up:
  grep myapp-task /var/log/syslog
  # or: journalctl -u cron | grep myapp-task

▶ Test manually as cron user:
  sudo -u appuser /opt/myapp/tasks/run-task.sh

💡 For complex scheduling with dependencies or resource limits → /linux-systemd-manager

Keep looking

Skills are one crate of 626,194. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.