Creating a reliable Linux backup script sounds simple — until tar starts throwing errors like:
tar: /etc/pdns*: Cannot stat: No such file or directory
tar: Exiting with failure status due to previous errors
In production environments, these small issues can cause failed backups, missed alerts, and serious operational risks.
In this guide, we’ll walk through:
- How to build a production-ready Linux backup script
- Why
tarwildcard errors happen - How to eliminate
Cannot statfailures - How to include OS version and installed packages in backups
- How to safely upload backups via FTP
- How to integrate email reporting
- Best practices for stable, enterprise-grade backup automation
All examples are anonymized and suitable for Oracle Linux, RHEL, CentOS, or similar distributions.
Why Backup Scripts Fail in Production
Many backup scripts fail due to one common mistake:
❌ Using wildcard paths that do not exist
Example:
/etc/pdns*
/etc/python*
If these directories don’t exist on the system, tar interprets them as literal paths and fails.
This results in:
tar: Cannot stat
tar: Exiting with failure status due to previous errors
In production, this means:
- Backup marked as failed
- Email alert triggered
- No valid backup uploaded
- False alarms or worse — silent failures
Best Practice: Never Use Unverified Wildcards
Instead of using:
/etc/php*
/etc/pdns*
Use only verified existing directories:
/etc/php.d
/etc/php.ini
/etc/httpd
/etc/my.cnf
/etc/my.cnf.d
This eliminates wildcard expansion issues entirely.
What a Production-Ready Backup Script Should Include
A professional Linux backup script should include:
1. MySQL/MariaDB dump
2. OS version information
3. Installed package list
4. Configuration files
5. Web application files
6. Email reporting
7. FTP or remote upload
8. Clean exit codes
Example: Stable Linux Backup Script (Zero Wildcard Errors)
Below is a simplified, production-safe example.
#!/bin/bash -e
EMAIL_TO="admin@example.com"
TODAY=$(date +%Y%m%d)
HOST=$(hostname -s)
BACKUP_FILE="/usr/local/src/${HOST}-${TODAY}.tgz"
LOGFILE="/var/log/backup_${TODAY}.log"
exec > "$LOGFILE" 2>&1
send_mail() {
mail -s "$1" -a "$LOGFILE" "$EMAIL_TO"
}
echo "Starting backup..."
# MySQL dump
mysqldump -u backupuser -p'StrongPassword' --all-databases > /usr/local/src/alldb.sql
# OS version
cat /etc/*release > /usr/local/src/os_version.info
# Installed packages
rpm -qa > /usr/local/src/installed_packages.info
# Safe directories (NO wildcards)
BACKUP_PATHS=(
/var/www
/etc/php.d
/etc/php.ini
/etc/hosts
/etc/httpd
/etc/my.cnf
/etc/my.cnf.d
/etc/sysconfig/network
/etc/firewalld
/usr/local/src
)
tar -czvf "$BACKUP_FILE" \
/usr/local/src/os_version.info \
/usr/local/src/installed_packages.info \
/usr/local/src/alldb.sql \
"${BACKUP_PATHS[@]}"
# FTP upload
ncftpput -u ftpuser -p ftppass ftp.example.com \
/NetBackup/server_folder "$BACKUP_FILE"
rm -f "$BACKUP_FILE"
send_mail "Backup successful on $HOST"
Why This Script Never Fails With “Cannot stat”
Because:
- No wildcard paths are used
- Only guaranteed existing directories are included
- Arrays (
"${BACKUP_PATHS[@]}") are used properly - No glob expansion issues
- No quoting mistakes
- No dynamic path assumptions
How to Include OS Version and Installed Packages
Including system metadata makes disaster recovery much easier.
OS Version
cat /etc/*release > os_version.info
This captures:
- Distribution name
- Version
- Kernel compatibility
Example output:
Oracle Linux Server release 7.9
NAME="Oracle Linux Server"
VERSION="7.9"
Installed Packages
rpm -qa > installed_packages.info
This allows full environment reconstruction.
Example snippet:
httpd-2.4.6-97.el7.x86_64
php-7.4.33-1.el7.x86_64
mysql-community-server-8.0.36.x86_64
During restore, you can reinstall:
dnf install package-name
How to Avoid FTP Upload Failures
Always use:
ncftpput -u USER -p PASS -m host remote_path file
Key points:
-mcreates remote directory if missing- Always verify remote path exists
- Use full absolute remote paths
- Do not dynamically generate folder names unless required
Example correct remote path:
/NetBackup/192.168.50.121_servername.domain
Common Mistakes That Break Backup Scripts
❌ Using multi-line strings with quotes
FILESTOBACKUP='
/etc/php*
'
❌ Using unverified wildcard directories
/etc/pdns*
❌ Relying on eval
$(eval echo $FILESTOBACKUP)
❌ Including directories that do not exist
❌ Forgetting to check exit codes
Incremental vs Full Backups
Full Backup
Includes everything:
tar -czvf backup-full.tgz ...
Incremental Backup
Includes only recently modified files:
find /var/www -mtime -1
Use incremental carefully — only if you understand retention strategy.
Recommended Backup Architecture
For production environments:
- Daily full backups
- Weekly offsite transfer
- 30-day retention
- Separate DB and file backups
- Encrypted archives (optional)
- Restore script included but NOT executed automatically
Disaster Recovery Workflow Example
- Provision new server
- Install base OS
- Install packages from
installed_packages.info - Restore database:
mysql < alldb.sql - Restore configuration files
- Restart services
- Validate applications
Final Recommendations
✔ Never use unsafe wildcards
✔ Always log output
✔ Always email status
✔ Always verify remote upload
✔ Include system metadata
✔ Keep restore script separate from backup logic
✔ Test restore regularly
Conclusion
Building a stable Linux backup script requires attention to small technical details — especially how tar handles wildcards.
The difference between:
/etc/pdns*
and
/etc/php.d
can determine whether your backup succeeds or silently fails.
A production-ready script should be:
- Deterministic
- Predictable
- Explicit
- Free from glob expansion errors
- Fully logged
- Fully monitored
When built correctly, your backup system becomes reliable, auditable, and disaster-ready.


