What the 'Permission denied' Error Means in cron
The Permission denied error (exit code 126 or 1) appears when the cron service attempts to execute a scheduled task, but the operating system blocks access to a file, directory, or resource. In reports, you will see lines like /bin/sh: 1: /path/to/script.sh: Permission denied or Failed to execute: Permission denied.
The scheduler runs in the background, under a specific user's identity, without an interactive terminal and with a limited set of environment variables. If the script or output file requires permissions that the cron daemon does not have, execution is halted immediately at startup.
Common Causes
- Missing execute bit on the script itself. The file may be readable but not marked as executable.
- Use of relative paths in the crontab or within a bash script.
crondoes not inherit the standardPATHfrom your profile. - Incorrect ownership permissions on logs or temporary files that the script attempts to write data to.
- Strict security policies (SELinux, AppArmor) that block the execution of non-standard binaries or access to specific directories.
- Attempting to run a task from the system
crontabwithout specifying a user. In/etc/crontaband/etc/cron.d/, an explicit username is required before the command.
Solutions
Solution 1: Setting Execute Permissions on the File
Check the current permissions on your script:
ls -l /opt/scripts/backup.sh
If the first column lacks an x symbol (e.g., -rw-r--r--), add it:
chmod +x /opt/scripts/backup.sh
💡 Tip: Ensure the interpreter specified in the shebang (
#!/bin/bashor#!/usr/bin/env python3) is also readable and executable by the user under which the task runs.
Solution 2: Replace Relative Paths with Absolute Ones
cron runs commands in a clean environment. If your crontab contains backup.sh or the script uses ./config.ini, the system will not find the file or will deny access to the current directory.
Replace all paths with full ones:
# Incorrect for cron
0 2 * * * my_script.sh
# Correct
0 2 * * * /usr/local/bin/my_script.sh
Inside the script itself, specify full paths to utilities explicitly:
#!/bin/bash
/usr/bin/rsync -avz /home/user/data /mnt/backup
/usr/sbin/logrotate /etc/logrotate.d/myapp
Solution 3: Configuring Permissions for Logs and Working Directories
A common cause of the error is an attempt to write command output (>> /var/log/cron.log) to a directory where the user lacks write permissions. Cron runs tasks under the identity of the crontab owner, so target directories must belong to that user:
sudo chown -R user:user /var/log/myapp
sudo chmod 755 /var/log/myapp
If you use output redirection in the crontab:
# Runs as user www-data, logs errors
*/15 * * * * www-data /usr/local/bin/fetch-data.sh >> /var/log/myapp/fetch.log 2>&1
Verify that the /var/log/myapp directory exists and is writable by the www-data user.
Solution 4: Checking Security Policies (AppArmor / SELinux)
On modern distributions, standard chmod permissions may be insufficient. If the system is protected by AppArmor (Ubuntu/Debian) or SELinux (RHEL/Alma/Rocky), they may silently block execution.
Check AppArmor status:
aa-status | grep my_script.sh
If the profile is in enforce mode, switch it to complain for testing:
sudo aa-complain /usr/local/bin/my_script.sh
For SELinux, use audit2why to analyze blocks:
sudo ausearch -m avc -ts recent | audit2why
Once a block is confirmed, add an exception or update policies via semodule.
Prevention
- Test in a clean environment. Before adding to cron, run the script with
env -i bash /path/to/script.sh. This mimics cron's empty environment and immediately reveals issues with paths or variables. - Use
sudo -u. To test execution permissions, runsudo -u target_user /path/to/script.sh. This shows the actual task restrictions before scheduling. - Configure notifications. Add
MAILTO="admin@example.com"at the top of your crontab so the system automatically sends error reports via email. - Lock down paths in scripts. Add
set -euo pipefailat the beginning of each bash script. This forces execution to stop on the first permission error or missing command, simplifying debugging.