What Does the "Permission Denied" Error in Cron Mean
When the task scheduler attempts to run your script, the Linux kernel rejects the request with the message /bin/sh: 1: /path/to/script: Permission denied and an exit code of 126. This is the system's standard response to insufficient permissions, incorrect file ownership, or restrictions enforced by security modules (SELinux/AppArmor).
Typically, this error appears in system logs (/var/log/syslog or /var/log/cron) or is emailed to the cron user if output redirection is configured. The task fails to execute, potentially causing data loss or missed backups without any explicit console notification.
Common Causes
- Missing execute permission. The file was downloaded, copied, or created with
644(-rw-r--r--) permissions, which only allow reading. - Ownership mismatch. The script is owned by
root, but the job was added to a regular user's crontab, or vice versa. - Use of relative paths. Cron executes commands from the user's home directory and does not initialize the
$PATHvariable. Symbols like~or./in the schedule will not resolve correctly. - SELinux or AppArmor restrictions. In enterprise distributions, security policies strictly control the execution of binaries from non-standard directories.
- Script located on a restricted filesystem. The partition is mounted with the
noexecflag (common for/tmpor/home).
How to Fix It
Method 1: Adjust Permissions and Ownership
First, verify that the file is actually executable and owned by the user running the cron job.
- Check the current permissions:
ls -l /opt/scripts/my_task.sh - If the
xbit is missing, add it:chmod +x /opt/scripts/my_task.sh - Change the owner to the account under which the task runs:
sudo chown username:username /opt/scripts/my_task.sh
💡 Tip: For system-wide jobs added via
sudo crontab -e, keeprootas the owner. For user-specific jobs, use a standard user account.
Method 2: Explicit Interpreter Invocation and Absolute Paths
Even with correct permissions, cron may fail to locate the executable or its dependencies. Always specify full, absolute paths.
Open the crontab editor:
crontab -e
Replace a line like this:
*/5 * * * * ./backup.sh
With this:
*/5 * * * * /usr/bin/bash /opt/scripts/backup.sh >> /var/log/backup_cron.log 2>&1
Redirecting output to a log file helps track subsequent errors without cluttering your inbox.
Method 3: Check and Configure Security Policies
If permissions are correct but the error persists, check whether a security module is blocking execution.
For SELinux (RHEL/CentOS/Fedora):
- Check the module status:
sestatus - Look for denials from the last 10 minutes:
ausearch -m AVC -ts recent - If the output shows
denied { execute }for your script, allow execution:
For a permanent fix, usesudo chcon -t bin_t /opt/scripts/my_task.shsemanage fcontextandrestorecon.
For AppArmor (Ubuntu/Debian):
- Check the profile status:
sudo aa-status - If the script falls under a strict profile, add an exception to
/etc/apparmor.d/local/usr.bin.cronor switch the profile to complain mode:sudo aa-complain /usr/bin/cron
Method 4: Run via run-parts or the sh Shell
Sometimes it's easier to bypass permission changes entirely by passing the script to a shell that handles execution internally. This is particularly useful if the file resides on a network drive or in /tmp.
*/10 * * * * /bin/sh /tmp/temp_script.sh
Alternatively, use the standard run-parts utility for directories:
0 * * * * /usr/bin/run-parts /etc/cron.hourly
This method automatically applies standard execution policies and simplifies job auditing.
Prevention Tips
To prevent this error from recurring, follow a few simple best practices when setting up automation:
- Store scripts in dedicated directories like
/opt/scripts/or/usr/local/bin/, where permissions are inherited predictably. - Include
#!/usr/bin/env bashor#!/bin/shat the top of every script so the system knows exactly which interpreter to use. - Avoid mounting directories with the
noexecflag if you plan to run automated tasks from them. - Regularly monitor scheduler logs:
sudo journalctl -u cron -forsudo journalctl -u crond -f. - Before adding a job to the schedule, always test the command manually as the target user:
su - username -c "/usr/bin/bash /opt/scripts/test.sh".