Cron cli workflow only
Cron is easiest to manage when you treat your crontab as configuration. This page shows two CLI-only workflows:
- append one job without opening an editor
- maintain a file (cronfile) and load it into cron
- Cron reads a per-user job list called the user crontab.
- You do not edit cron spool files directly; you use the
crontabcommand. - File-based workflows are easier to version, review, and audit.
Mental model of cron
- Cron reads jobs from a small database per user (called user crontab).
- You never edit that database file directly.
- Instead, you use the
crontabcommand to send content to Cron.
So your job is just:
“Prepare the content” → “Give it to cron using crontab”.
Workflow: quick method (no file, CLI only)
This is for: “I just want one job quickly, I don’t care about saving it as a file.”
Goal
Run /usr/local/bin/backup.sh every day at 2:00 AM.
Add a job by appending
( crontab -l 2>/dev/null; echo "0 2 * * * /usr/local/bin/backup.sh" ) | crontab -
What this does (in plain language):
crontab -l→ list existing cron jobs for your user; echo "0 2 * * * ..."→ add a new line (your new job)(...) | crontab -→ send everything back into cron as the new job list
So:
- If there were previous jobs → they are kept
- Your new job → gets appended
Verify the active crontab
crontab -l
You should now see:
0 2 * * * /usr/local/bin/backup.sh
No file created
No editor
Fully CLI
Workflow: file-based method (cronfile -> crontab)
This is the one you were asking about:
where is the file path should i open?
should i created its file first?
Let’s answer by doing it step-by-step.
Scenario
You want to create a cron file, store it safely (like part of your project/course), and then load it into Cron.
We will:
- Create a folder for cron files
- Create a
.cronfile - Put cron jobs inside
- Load it into cron
- Verify it
Choose a folder for your cron files
Good practice: keep them in your home directory.
mkdir -p ~/cronjobs
~= your home directory (e.g./home/yourname)~/cronjobsis now your personal cron config folder
Create the cron file
Let’s call it wp_maintenance.cron and store it in that folder.
nano ~/cronjobs/wp_maintenance.cron
If you don’t like nano, you can use nvim or micro.
But I’ll assume nano for now.
Put cron jobs inside that file
Inside nano, paste something like this:
# Use bash and a proper PATH
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Every day at 1:00 AM - backup database
0 1 * * * /usr/local/bin/wp-db-backup.sh >> /var/log/wp-db-backup.log 2>&1
# Every 15 minutes - run WordPress cron
*/15 * * * * /usr/bin/php /var/www/html/wp-cron.php >> /var/log/wp-cron.log 2>&1
Then:
- Save in nano:
Ctrl + O→ Enter - Exit nano:
Ctrl + X
Now you have a file at:
~/cronjobs/wp_maintenance.cron
This is your file, you own it, you can put it in Git, copy, backup, etc.
Cron does not run from this file. We still need to load it.
Load this file into cron
Run:
crontab ~/cronjobs/wp_maintenance.cron
What happened?
crontab <file>tells cron: “Replace my user’s cron jobs with the content of this file.”- Cron copies the content into its own storage (typically in
/var/spool/cron/...), but you don’t touch those spool files directly.
Now your active cron config = whatever is inside ~/cronjobs/wp_maintenance.cron at the time you ran the command.
Verify active cron jobs
crontab -l
You should see:
SHELL=/bin/bash
PATH=...
0 1 * * * /usr/local/bin/wp-db-backup.sh >> /var/log/wp-db-backup.log 2>&1
*/15 * * * * /usr/bin/php /var/www/html/wp-cron.php >> /var/log/wp-cron.log 2>&1
If yes → your cron file is now live.
Update later
Let’s say you want to change the time or add a new task.
- Edit the same file:
nano ~/cronjobs/wp_maintenance.cron
- Modify / add lines
- Save
- Load again:
crontab ~/cronjobs/wp_maintenance.cron
Important:
Every time you run crontab ~/cronjobs/wp_maintenance.cron, cron replaces the old config with whatever is currently in that file.
This is actually great:
- You use the file as the source of truth
- Cron is just a runner, not your editor
Visual summary of the file-based workflow
You edit THIS file (your file):
~/cronjobs/wp_maintenance.cron
|
| crontab ~/cronjobs/wp_maintenance.cron
v
Cron's internal storage (do not edit directly)
Common questions:
- File location: pick a single folder (example:
~/cronjobs/) and keep your cronfiles there. - Creation order: create the file, add cron lines, save, then install it with
crontab /path/to/file.
Practice exercise
-
Make a cron folder:
practice-create-cronjobs-dir.shmkdir -p ~/cronjobs -
Create a test cron file:
practice-create-test-cronfile.shnano ~/cronjobs/test.cron -
Put this inside:
practice-test-cron-entry.cron* * * * * echo "Cron is working at $(date)" >> /tmp/cron_test.log 2>&1 -
Load it:
practice-install-test-cronfile.shcrontab ~/cronjobs/test.cron -
Wait 1–2 minutes, then check:
practice-check-cron-test-log.shtail /tmp/cron_test.log
If you see timestamps in /tmp/cron_test.log, cron is executing the entry successfully.
Scaling beyond a few jobs
Once you have several cron jobs, treat the crontab as configuration. That means: keep a single cronfile and install it, instead of "appending one-off lines" repeatedly.
Common failure modes of ad-hoc appends:
- jobs drift (you forget what is installed)
- schedules are hard to review
- duplication and overlap are easy to introduce
- rollback is painful
For 10+ jobs, use the file-based workflow in the next section.
Pre-apply safety checklist
Before you run crontab /path/to/cronfile:
- confirm the cronfile starts with
SHELL=and a fullPATH= - confirm every command uses absolute paths
- confirm every job logs somewhere you can read (or uses journald via systemd)
- confirm long-running jobs use
flock(or are otherwise safe to overlap)
Keep a local backup of the currently-installed crontab:
mkdir -p ~/cronjobs/backups
crontab -l > ~/cronjobs/backups/crontab.$(date +%F_%H%M%S).txt
ls -lah ~/cronjobs/backups | tail -n 20
Running crontab /path/to/cronfile replaces the entire user crontab.
If you forget a line, that job is no longer scheduled.
Recommended workflow: file-based cron management (cronfile -> crontab)
This is a standard maintainable approach for production cron.
Why this scales for many cron jobs
You store all jobs in one clean file
Easy to update, delete, reorder
Can be version-controlled (Git)
Easily copy/paste to other servers
No scrolling through crontab -e
No repeated CLI commands
Bulletproof and predictable
Perfect for backup/migration
Fully scriptable
Easy to troubleshoot
File-based cron workflow
Create a directory for cron files
This keeps things organized:
mkdir -p ~/cronjobs
Create your main cron file
Example name:
nano ~/cronjobs/server.cron
Put your cron jobs into one file
Example server.cron:
# Environment needed for cron
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# ---------------------
# BACKUPS
# ---------------------
0 2 * * * /usr/local/bin/backup-files.sh
0 3 * * * /usr/local/bin/backup-db.sh
# ---------------------
# WORDPRESS MAINTENANCE
# ---------------------
*/15 * * * * /usr/bin/php /var/www/html/wp-cron.php
0 */6 * * * /usr/local/bin/wp-security-scan.sh
# ---------------------
# MONITORING
# ---------------------
*/10 * * * * /usr/local/bin/monitor-load.sh
*/5 * * * * /usr/local/bin/check-disk.sh
# ---------------------
# CLEANUP
# ---------------------
0 4 * * * /usr/local/bin/purge-temp.sh
0 4 * * * /usr/local/bin/rotate-logs.sh
# ---------------------
# CUSTOM TASKS
# ---------------------
30 * * * * /usr/local/bin/api-ping.sh
45 */2 * * * /usr/local/bin/report-email.sh
This is your master cron definition file.
Install the cron file into your user crontab
Load it into cron:
crontab ~/cronjobs/server.cron
This overwrites existing jobs with this file — so cron always reflects your file’s content.
Verify the active crontab
crontab -l
You should now see all jobs exactly as in server.cron.
Why this workflow scales
Cleaner
Everything is in one single file that you can organize, format, and comment.
Safer
You don’t risk accidentally duplicating or deleting cron jobs with echo pipelines.
Scalable
10, 20, 50 cron jobs — easily maintained in one file.
Track changes
You can commit it to Git:
git add server.cron
git commit -m "Updated cron schedules"
Easy rollback
crontab ~/cronjobs/old_server.cron
Easy to copy to new servers
scp ~/cronjobs/server.cron user@newserver:/tmp/
ssh user@newserver "crontab /tmp/server.cron"
This is very powerful.
If you have many cron jobs
~/cronjobs/server.cron
↓
crontab ~/cronjobs/server.cron
↓
crontab -l
This becomes your automation pipeline for cron.
Optional: deploy cronfiles with automation tools
Instead of manually editing files:
Create a cron deployment script:
#!/usr/bin/env bash
set -euo pipefail
crontab - < ~/cronjobs/server.cron
crontab -l
Or use cloud-init, Ansible, Chef, Puppet — they all expect the file-based workflow.
Takeaway
If you have more than 10 cron jobs, the best workflow is:
Use a single cron definition file (cronfile) and load it using crontab cronfile.
This gives you:
- clean structure
- repeatability
- scalability
- automation
- version control
- reliability
This is the professional standard.
Workflow preference: edit via SFTP, apply via CLI
If you prefer not to use interactive terminal editors, the file-based workflow still works well.
You can:
- edit
~/cronjobs/server.cronusing SFTP and your editor of choice - apply changes using a single CLI command
This keeps the "definition" of cron jobs in a file you can review and copy to other servers.
The rest of this page provides a repeatable setup and an update routine.
One-Time Setup (per server)
You only do this once.
Create a folder to store your cron files
SSH into the server (just to run commands, not to edit):
mkdir -p ~/cronjobs
This folder will be your “Cron Source of Truth”.
- Path:
~/cronjobs - You will open/edit files inside this folder via SFTP + editor, not via terminal.
Your Main Cron File
Create the cron file (from your local machine via SFTP)
Via SFTP (FileZilla, WinSCP, VS Code Remote, etc.):
- Navigate to:
/home/youruser/cronjobs/ - Create a new file:
server.cron
Edit server.cron on your local editor and add something like:
# Use bash and set PATH so commands work properly
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# ───────────────
# BACKUP TASKS
# ───────────────
0 2 * * * /usr/local/bin/backup-files.sh >> /var/log/backup-files.log 2>&1
0 3 * * * /usr/local/bin/backup-db.sh >> /var/log/backup-db.log 2>&1
# ───────────────
# WORDPRESS TASKS
# ───────────────
*/15 * * * * /usr/bin/php /var/www/html/wp-cron.php >> /var/log/wp-cron.log 2>&1
0 4 * * * /usr/local/bin/wp-security-scan.sh >> /var/log/wp-security.log 2>&1
# ───────────────
# MONITORING
# ───────────────
*/10 * * * * /usr/local/bin/monitor-load.sh >> /var/log/monitor-load.log 2>&1
0 * * * * /usr/local/bin/check-disk.sh >> /var/log/check-disk.log 2>&1
# ───────────────
# CLEANUP
# ───────────────
30 3 * * * /usr/local/bin/cleanup-temp.sh >> /var/log/cleanup-temp.log 2>&1
You can add as many cron jobs as you want here.
The key is: you edit this file using your GUI editor via SFTP, not in the terminal.
Activating / Updating Cron (Terminal but Non-Interactive)
Whenever you’re done editing server.cron in your GUI editor and saved it over SFTP, you “apply” it with just one very simple command.
Load this file into cron
SSH to your server and run:
crontab ~/cronjobs/server.cron
That’s it.
- No
crontab -e - No
nano - No interactive editing
- Just a single command that says:
“Dear cron, use whatever is in ~/cronjobs/server.cron as my job list.”
(Optional) Verify what is active
Still non-interactive, just read-only:
crontab -l
You should see exactly what’s in server.cron.
Your Ongoing Workflow (Daily / Weekly Use)
From now on, your routine looks like this:
Every time you want to add/change/remove a cron job
-
Open SFTP from your laptop → connect to server
-
Navigate to:
~/cronjobs/ -
Open
server.cronin your editor (VS Code / Sublime / Notepad++ / etc.) -
Edit your cron lines (add new job, change schedule, comment out something, etc.)
-
Save the file.
-
Go to terminal and run:
apply-cronfile.shcrontab ~/cronjobs/server.cron -
(Optional) Check:
verify-active-crontab.shcrontab -l
No interactive editing at all.
All edits done through normal file editing via SFTP.
Example: Real Change Scenario
Let’s say you want to:
- Change backup time from
2:00to1:00 - Add a new cron to ping a monitoring URL every 5 min
Step-by-step with your workflow
-
Open
~/cronjobs/server.cronvia SFTP in your editor. -
Change:
before-change.cron0 2 * * * /usr/local/bin/backup-files.sh ...to:
after-change.cron0 1 * * * /usr/local/bin/backup-files.sh ... -
Add a new line:
add-monitoring-line.cron*/5 * * * * /usr/bin/curl -s https://status.example.com/ping >> /var/log/status-ping.log 2>&1 -
Save file.
-
SSH and run:
apply-updated-cronfile.shcrontab ~/cronjobs/server.cron -
Done. New config is live.
Bonus: Make It Even Easier (One Command Alias)
So you don’t have to remember the long path every time, you can create an alias once:
echo 'alias cronapply="crontab ~/cronjobs/server.cron"' >> ~/.bashrc
source ~/.bashrc
Now your activation flow becomes:
-
Edit
server.cronover SFTP -
In terminal, just run:
use-cronapply-alias.shcronapply
Optional: Keep Different Cron Files (Staging / Production / Presets)
You can store multiple files:
~/cronjobs/staging.cron~/cronjobs/production.cron~/cronjobs/wp-basic.cron~/cronjobs/wp-advanced.cron
To apply them:
crontab ~/cronjobs/staging.cron
# or
crontab ~/cronjobs/production.cron
Still the same pattern:
Edit via SFTP → Apply with one command
Summary of Your Non-Interactive Cron Workflow
-
Where is the file I edit?
~/cronjobs/server.cron(or any.cronfile you decide) -
How do I edit it?
Using SFTP + your favorite editor (no terminal editing).
-
How do I activate the cron jobs?
Run:
apply-cronfile-from-summary.shcrontab ~/cronjobs/server.cron -
How do I see what’s active?
Run:
list-current-crontab-from-summary.shcrontab -l -
Do I ever need
crontab -e?No. Your workflow completely avoids it.
Example: server.cron template
This example cronfile uses:
- explicit
SHELLandPATH - categorized jobs
- stdout/stderr logging appended to files
# Use bash and set PATH so commands work properly
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# BACKUP TASKS
0 2 * * * /usr/local/bin/backup-files.sh >> /var/log/backup-files.log 2>&1
0 3 * * * /usr/local/bin/backup-db.sh >> /var/log/backup-db.log 2>&1
# WORDPRESS TASKS
*/15 * * * * /usr/bin/php /var/www/html/wp-cron.php >> /var/log/wp-cron.log 2>&1
0 4 * * * /usr/local/bin/wp-security-scan.sh >> /var/log/wp-security.log 2>&1
# MONITORING
*/10 * * * * /usr/local/bin/monitor-load.sh >> /var/log/monitor-load.log 2>&1
0 * * * * /usr/local/bin/check-disk.sh >> /var/log/check-disk.log 2>&1
# CLEANUP
30 3 * * * /usr/local/bin/cleanup-temp.sh >> /var/log/cleanup-temp.log 2>&1
The next sections explain what SHELL, PATH, and redirection operators mean in a cronfile.
SHELL=/bin/bash
This line is at the top of the cron file:
SHELL=/bin/bash
What it means:
-
It tells cron:
“When you run my commands, use bash as the shell.”
Think of SHELL as “what language the command is spoken in”.
/bin/bash= the bash shell (most common on Linux)- If you don’t set this, cron might use
/bin/sh, which can behave a bit differently.
So:
- With this line, all cron jobs under it will run using bash.
- This helps when you use bash syntax in your scripts/commands.
You don’t put quotes here in the file. It’s exactly:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
This line is:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
What PATH means:
PATHis a list of folders where Linux looks for commands.- When you type
phporcurlorbackup-files.sh, the system checks these folders in order to find that program.
In a normal terminal, your PATH is usually quite full.
But in cron, the default PATH is often very small → many commands that work in your terminal don’t work in cron unless you set PATH or use full paths.
So we set:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
This tells cron:
“When you see a command like php or curl or gzip, look for them in these folders.”
The different parts are:
/usr/local/sbin/usr/local/bin/usr/sbin/usr/bin/sbin/bin
They’re separated by : — not by spaces.
Again:
No quotes in the file. Just exactly that line.
> and >> (these control where the output goes)
These are shell redirection operators.
They decide what happens to the output (text) that your program prints.
> (overwrite)
Example:
echo "hello" > /tmp/test.log
- If
/tmp/test.logdoes not exist → it creates it - If it does exist → it overwrites it (deletes old content, keeps only “hello”)
So in cron, if you used:
0 2 * * * /usr/local/bin/backup-files.sh > /var/log/backup-files.log
Then:
- Every time it runs, it replaces the content of
/var/log/backup-files.logwith the latest output only.
>> (append)
Example:
echo "hello" >> /tmp/test.log
- If file doesn’t exist → create it
- If file exists → add “hello” to the end, keep old content
So in your cron example:
0 2 * * * /usr/local/bin/backup-files.sh >> /var/log/backup-files.log 2>&1
>> /var/log/backup-files.log means:
“Whatever the program prints, add it to the end of this log file. Never delete the old logs.”
This is usually what we want for logs:
- You get a history of runs.
- You can see yesterday’s output, last week’s, etc.
2>&1 (combine errors + normal output)
Programs can output two kinds of text:
- normal output → called
stdout(file descriptor1) - error messages → called
stderr(file descriptor2)
By default:
>and>>affect only normal output (stdout), not errors.
So:
... >> /var/log/backup-files.log
This will log normal output, but error messages might still go somewhere else (like email or system logs).
To capture both normal output and errors into the same log file, we add:
2>&1
What 2>&1 means:
2= stderr (error messages)1= stdout (normal output)2>&1= “send error output (2) to the same place as normal output (1)”
So:
>> /var/log/backup-files.log 2>&1
Means:
>> /var/log/backup-files.log→ send normal output to this file (append)2>&1→ send error output to the same place as normal output
Result:
Both normal messages and error messages go into /var/log/backup-files.log.
Putting it all together in your line
Let’s take one of your cron lines:
0 2 * * * /usr/local/bin/backup-files.sh >> /var/log/backup-files.log 2>&1
Breakdown:
-
0 2 * * *→ Run at 02:00 every day
-
/usr/local/bin/backup-files.sh→ Run this script
-
>> /var/log/backup-files.log→ Append all normal output to
/var/log/backup-files.log -
2>&1→ Send all error messages to the same file as normal output
And at the top of the file:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Means:
- Use
bashfor running the cron commands - Set a good PATH so commands can be found
What you actually need to do
For your workflow:
-
Copy this block exactly at the top of your cron file:
cronfile-header.envSHELL=/bin/bashPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin -
For each job, use this structure:
cron-entry-template.cron<schedule> <command> >> /var/log/some-log.log 2>&1
Example:
*/15 * * * * /usr/bin/php /var/www/html/wp-cron.php >> /var/log/wp-cron.log 2>&1
-
Edit/save the file via SFTP
-
Apply with:
apply-cronfile.shcrontab ~/cronjobs/server.cron
Practice exercises
Use these exercises to practice the workflow without editing crontab interactively.
Exercise: add a daily backup job
Goal:
- run a backup script daily at 01:00
- append logs
- avoid overlap
0 1 * * * flock -n /var/lock/wp-backup.lock /usr/local/bin/wp-backup-run >>/var/log/wp-backup.log 2>&1
Exercise: add a weekly cleanup job
Goal:
- prune old backups weekly
- log output
0 4 * * 0 /usr/bin/find /backups -type f -name 'wp-*.tar.*' -mtime +30 -delete >>/var/log/backup-prune.log 2>&1
Practice pruning with -print before you use -delete.
Exercise: verify the applied crontab
crontab -l
Exercise: keep a copy of your last applied cronfile
If you maintain cronfiles in a folder, keep a dated copy.
cp -a ~/cronjobs/server.cron ~/cronjobs/server.cron.$(date +%F)
ls -lah ~/cronjobs | tail -n 30
Troubleshooting
Job works manually but fails in cron
Common causes:
- PATH differences
- different shell (
/bin/shvs/bin/bash) - missing permissions
Fix patterns:
- use absolute paths
- set
SHELL=andPATH=at the top of the cronfile - redirect output to logs
Job overlaps
If the job sometimes runs long, add flock.
flock -n /var/lock/myjob.lock /usr/local/bin/myjob
Next steps
- Cron fundamentals:
opt/docker-data/apps/docusaurus/site/docs/server/linux-server/11-automation-task-execution/cron.mdx - Redirecting output and logs:
opt/docker-data/apps/docusaurus/site/docs/server/linux-server/11-automation-task-execution/redirecting-output-and-logs.mdx - Cron vs systemd timers:
opt/docker-data/apps/docusaurus/site/docs/server/linux-server/11-automation-task-execution/cron-vs-systemd.mdx