Auto-Clean Temp Files and Old Uploads by Age
Temporary files and user uploads accumulate silently on servers until a disk-full event takes down your application. Running a scheduled cleanup with find -mtime lets you set a maximum file age and reclaim space on autopilot. Pairing it with a CronJobPro heartbeat means you get alerted if the job silently stops running, not just when your disk is already full.
Schedule
0 3 * * *Every day at 3:00 AM server time
Setup
- 1
Identify the directories to clean
Decide which paths hold expendable files: common candidates are /tmp, /var/tmp, your application upload staging folder (e.g. /var/www/app/uploads/tmp), and any export or cache directories. Never point the script at directories that contain user data you have not already archived elsewhere.
- 2
Choose a safe minimum age
Pick a minimum file age in days using -mtime +N, where N is the number of days old a file must be before deletion. For /tmp use 1-2 days; for user upload staging use 7 days; for processed export files use 30 days. More conservative values reduce the risk of deleting a file that is still in use.
- 3
Create the cleanup script on your server
Save the script below to /usr/local/bin/cleanup-old-files.sh and make it executable with: chmod +x /usr/local/bin/cleanup-old-files.sh. Edit the DIRS and MAX_AGE_DAYS variables at the top to match your environment. Add your CronJobPro heartbeat token to the HEARTBEAT_URL variable.
- 4
Create a CronJobPro heartbeat monitor
In CronJobPro, create a new Heartbeat monitor with a period of 1 day and a grace period of 30 minutes. Copy the unique ping URL (https://cronjobpro.com/ping/<your-token>) into the HEARTBEAT_URL variable in the script. Set up an alert channel (email, Slack, etc.) on the monitor so you are notified if a ping does not arrive within the expected window.
- 5
Register the cron entry
Add the job to root's crontab with: crontab -e, then add the line: 0 3 * * * /usr/local/bin/cleanup-old-files.sh >> /var/log/cleanup-old-files.log 2>&1. The log file lets you review what was deleted and catch permission errors without needing to check the server manually.
The script
bash
#!/usr/bin/env bash
set -euo pipefail
# ---- Configuration ----
# Space-separated list of directories to clean
DIRS="/tmp /var/tmp /var/www/app/uploads/tmp"
# Delete files older than this many days
MAX_AGE_DAYS=7
# CronJobPro heartbeat URL (replace <token> with your actual token)
HEARTBEAT_URL="https://cronjobpro.com/ping/<token>"
# Optional: minimum file size to delete (avoids touching tiny lock files)
# Set to 0c to delete files of any size
MIN_SIZE="0c"
# ---- End configuration ----
LOG_PREFIX="[cleanup-old-files $(date -u '+%Y-%m-%dT%H:%M:%SZ')]"
TOTAL_FREED=0
EXIT_CODE=0
for DIR in $DIRS; do
if [ ! -d "$DIR" ]; then
echo "$LOG_PREFIX SKIP $DIR (not found)"
continue
fi
echo "$LOG_PREFIX Scanning $DIR for files older than ${MAX_AGE_DAYS} days"
# Collect matching files before deletion so we can log and sum sizes
while IFS= read -r -d '' FILE; do
SIZE=$(stat -c%s "$FILE" 2>/dev/null || echo 0)
echo "$LOG_PREFIX DELETE $FILE (${SIZE} bytes)"
rm -f -- "$FILE" || { echo "$LOG_PREFIX WARN could not delete $FILE"; EXIT_CODE=1; }
TOTAL_FREED=$((TOTAL_FREED + SIZE))
done < <(find "$DIR" -maxdepth 5 -type f \
-mtime +"$MAX_AGE_DAYS" \
-size +"$MIN_SIZE" \
-print0 2>/dev/null)
# Also remove empty subdirectories left behind
find "$DIR" -mindepth 1 -maxdepth 5 -type d -empty -delete 2>/dev/null || true
done
FREED_MB=$(echo "scale=2; $TOTAL_FREED / 1048576" | bc)
echo "$LOG_PREFIX Done. Total freed: ${FREED_MB} MB (exit_code=${EXIT_CODE})"
# Report outcome to CronJobPro
if [ "$EXIT_CODE" -eq 0 ]; then
curl -fsS --retry 3 "${HEARTBEAT_URL}" -o /dev/null
else
curl -fsS --retry 3 "${HEARTBEAT_URL}/fail" -o /dev/null
fi
exit "$EXIT_CODE"
Monitor it
Create a Heartbeat monitor in CronJobPro with a period of 24 hours and a grace period of at least 30 minutes to account for the time the script takes to run. Paste the generated ping URL (https://cronjobpro.com/ping/your-token) into the HEARTBEAT_URL variable in the script. On a successful run the script calls the base ping URL; if any deletion fails it calls /ping/your-token/fail instead, so you can distinguish a job that ran but hit errors from one that never ran at all. If CronJobPro does not receive a ping within the 24-hour period plus the grace window, it fires an alert to whichever channel you configured (email, Slack, Discord, Teams, PagerDuty, Opsgenie, or a custom webhook). This means a silent failure — cron not firing, the server being down, or a script crash before the ping — will still reach you, unlike simply reading logs after the fact.
Frequently asked questions
Is it safe to run find -delete on /tmp directly?
It is safe as long as you use -mtime +1 or higher so you avoid deleting files that belong to currently running processes. Using -maxdepth limits the scan and avoids following unexpected symlinks deep into the filesystem. Always test with -print instead of deletion first to review what would be removed.
Why does the script use find with -print0 and a while loop instead of find -delete?
The while loop approach lets the script log every deleted file and accumulate the freed-space total before removing anything. It also allows per-file error handling so one undeletable file does not abort the entire run. find -delete is slightly faster but gives no per-file output and makes auditing harder.
What happens if the server is down when the job is scheduled?
Standard system cron does not retry missed jobs. CronJobPro will alert you because no heartbeat ping arrives within the period plus grace window. You can then run the script manually or reschedule it. If you want automatic retry on server restart, look at anacron or a process supervisor like systemd timers with Persistent=true.
How do I make sure I am not accidentally deleting files that are still being written?
Use a conservative -mtime value appropriate for your workflow. For upload staging, files that are actively being uploaded will have a very recent modification time and will not match -mtime +7. You can also add -not -newer /some/reference/file or check that no process has the file open with lsof before deletion, though for most staging directories a generous age threshold is sufficient.