最可靠方案是用 exec() 调用 tar 命令备份,必须用 escapeshellarg() 转义路径防注入;确保 PHP 进程有读源目录、写目标目录权限;定时任务须用系统 cron 运行 CLI 脚本,并自动清理旧备份。

PHP 用 exec() 调用系统命令备份文件夹最可靠
PHP 原生没有“打包整个目录并压缩”的函数,ziparchive 类虽能打 zip,但对符号链接、权限、空子目录支持弱,且无法保留原始修改时间。生产环境推荐交由系统命令处理,更稳定、可控。
常见错误是直接拼接路径进 exec(),导致空格或特殊字符(如 My Folder/)引发命令截断或注入。必须用 escapeshellarg() 包裹所有用户可控路径:
exec('tar -czf ' . escapeshellarg('/backup/site_' . date('Ymd_His') . '.tar.gz') . ' ' . escapeshellarg('/var/www/html'), $output, $return_code);-
tar -czf:创建 gzip 压缩的 tar 包,比 zip 更兼容 Unix 权限和软链 - 确保 PHP 进程有读取源目录、写入目标目录的权限(常被忽略,报错
Permission denied) - Web 服务器(如 Apache/Nginx)通常以
www-data或nginx用户运行,需确认该用户能访问目标路径
用 RecursiveIteratorIterator 手动复制文件夹要小心硬链接和权限丢失
如果必须纯 PHP 实现(如共享主机禁用 exec),可用迭代器遍历复制,但默认不会保留文件 mtime、chmod、owner,也跳过硬链接(is_link() 判定的是软链,硬链接会被当成普通文件复制两份)。
关键点:
立即学习“PHP免费学习笔记(深入)”;
- 用
touch($dst, filemtime($src))恢复修改时间 - 用
chmod($dst, fileperms($src))复制权限(注意:PHP 可能无权设某些位,如 setuid) - 遇到
is_dir($path)就mkdir(),遇到is_file($path)就copy(),别漏掉is_readable()检查 - 跳过
.和..目录项,否则会无限递归
定时自动备份得靠系统 cron,别信 sleep() 或 Web 请求触发
PHP 脚本通过浏览器访问来“自动”备份,本质是手动触发,不可靠。超时(max_execution_time)、内存限制、用户没访问就永远不会执行。
正确做法是把备份脚本写成 CLI 模式(加 #!/usr/bin/env php 头),然后加到系统 cron:
0 2 * * * /usr/bin/php /path/to/backup.php > /dev/null 2>&1
- 务必用绝对路径调用
php(which php查),避免 cron 环境找不到解释器 - 脚本开头加
chdir(__DIR__);,防止相对路径失效 - 日志重定向到文件(如
> /var/log/backup.log 2>&1)比丢进/dev/null更利于排障
备份文件命名和清理不处理,三个月后磁盘就满
只生成新备份不删旧包,是线上事故高发原因。别依赖人工清理。
建议在备份脚本末尾加清理逻辑:
array_map('unlink', array_slice(array_filter(glob('/backup/*.tar.gz'), 'is_file'), 0, -7));- 上面代码保留最近 7 个备份,其余删除(
glob()结果未排序,实际应先usort()按文件名或filemtime()排序) - 更稳妥用
find /backup -name "*.tar.gz" -mtime +7 -delete交由系统命令处理 - 命名里带日期(如
site_20240520.tar.gz)方便按时间筛选,别用随机字符串
权限、路径、清理——这三个点漏掉任何一个,备份脚本上线后都可能悄无声息地失效。











