Ubuntu PHP 备份与恢复实操指南
一 备份范围与策略
二 用 PHP 实现一键备份与恢复
备份脚本 backup.php(示例)
/usr/bin/php /var/www/html/tools/backup.php<?php
// 配置区
$backupRoot = '/var/backups/php_site'; // 备份根目录(确保可写)
$wwwRoot = '/var/www/html'; // 网站根目录
$dbHost = 'localhost';
$dbName = 'your_db';
$dbUser = 'your_user';
$dbPass = 'your_password';
$keepDays = 7;
$encrypt = true; // 是否加密
$cipher = 'aes-256-cbc';
// 初始化
date_default_timezone_set('Asia/Shanghai');
set_time_limit(300);
if (!is_dir($backupRoot)) mkdir($backupRoot, 0700, true);
$ts = date('Ymd_His');
$log = "$backupRoot/backup_$ts.log";
$zip = "$backupRoot/site_$ts.zip";
$sql = "$backupRoot/db_$ts.sql";
$enc = "$zip.enc";
function logMsg($msg, $log) { file_put_contents($log, date('Y-m-d H:i:s') . " $msg\n", FILE_APPEND); }
// 1) 打包网站文件
$zipOk = false;
if (class_exists('ZipArchive')) {
$zipArc = new ZipArchive();
if ($zipArc->open($zip, ZipArchive::CREATE | ZipArchive::OVERWRITE) === TRUE) {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($wwwRoot, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($wwwRoot) + 1);
$zipArc->addFile($filePath, $relativePath);
}
}
$zipOk = $zipArc->close();
}
}
logMsg($zipOk ? "ZIP created: $zip" : "ZIP failed", $log);
// 2) 导出数据库
$dumpOk = false;
$cmd = sprintf(
'mysqldump --single-transaction --routines --triggers --default-character-set=utf8mb4 -h%s -u%s -p%s %s > %s 2>>%s',
escapeshellarg($dbHost), escapeshellarg($dbUser), escapeshellarg($dbPass), escapeshellarg($dbName), $sql, $log
);
system($cmd, $ret);
$dumpOk = ($ret === 0);
logMsg($dumpOk ? "DB dumped: $sql" : "DB dump failed", $log);
// 3) 可选加密
$finalArchive = $zip;
if ($encrypt && $zipOk) {
$key = random_bytes(32); // 生产环境请安全保存此密钥
$iv = random_bytes(16);
$data = file_get_contents($zip);
$encData = openssl_encrypt($data, $cipher, $key, OPENSSL_RAW_DATA, $iv);
file_put_contents($enc, $iv . $encData);
$finalArchive = $enc;
@unlink($zip);
logMsg("Encrypted -> $enc", $log);
}
// 4) 清理过期
$files = glob("$backupRoot/*.{zip,sql,enc,log}", GLOB_BRACE);
foreach ($files as $f) {
if (filemtime($f) < time() - $keepDays * 86400) {
@unlink($f);
logMsg("Removed expired: $f", $log);
}
}
echo "Backup finished. See $log\n";
恢复脚本 restore.php(示例)
/usr/bin/php /var/www/html/tools/restore.php /var/backups/php_site/site_YYYYMMDD_HHIISS.zip[.enc]<?php
// 配置区(与备份端一致)
$wwwRoot = '/var/www/html';
$dbHost = 'localhost';
$dbName = 'your_db';
$dbUser = 'your_user';
$dbPass = 'your_password';
$cipher = 'aes-256-cbc';
date_default_timezone_set('Asia/Shanghai');
set_time_limit(600);
function logMsg($msg, $log) { file_put_contents($log, date('Y-m-d H:i:s') . " $msg\n", FILE_APPEND); }
if ($argc < 2) die("Usage: php restore.php <backup.zip|backup.zip.enc>\n");
$archive = $argv[1];
$log = dirname($archive) . '/restore_' . date('Ymd_His') . '.log';
// 1) 解密
$work = $archive;
if (pathinfo($archive, PATHINFO_EXTENSION) === 'enc') {
$key = ''; // 从安全位置读取密钥(不要硬编码在代码中)
if (empty($key) || strlen($key) !== 32) die("Missing or invalid 32-byte key.\n");
$data = file_get_contents($archive);
$iv = substr($data, 0, 16);
$enc = substr($data, 16);
$dec = openssl_decrypt($enc, $cipher, $key, OPENSSL_RAW_DATA, $iv);
if ($dec === false) die("Decrypt failed.\n");
$work = $archive . '.dec.zip';
file_put_contents($work, $dec);
logMsg("Decrypted -> $work", $log);
}
// 2) 解压覆盖
$zip = new ZipArchive();
if ($zip->open($work) !== TRUE) die("Cannot open archive: $work\n");
if (!$zip->extractTo($wwwRoot)) die("Extract failed.\n");
$zip->close();
logMsg("Extracted to $wwwRoot", $log);
if ($work !== $archive) @unlink($work);
// 3) 导入数据库(查找同目录 .sql)
$sqlFile = null;
foreach (glob(dirname($archive) . '/*.sql') as $f) {
if (time() - filemtime($f) < 86400 * 2) { $sqlFile = $f; break; } // 取最近
}
if (!$sqlFile) die("No .sql found near $archive\n");
$cmd = sprintf(
'mysql --default-character-set=utf8mb4 -h%s -u%s -p%s %s < %s 2>>%s',
escapeshellarg($dbHost), escapeshellarg($dbUser), escapeshellarg($dbPass), escapeshellarg($dbName), $sqlFile, $log
);
system($cmd, $ret);
logMsg($ret === 0 ? "DB imported: $sqlFile" : "DB import failed", $log);
echo "Restore finished. See $log\n";
定时任务(crontab)
0 2 * * * /usr/bin/php /var/www/html/tools/backup.php三 系统级备份与恢复 PHP 配置
php --ini/etc/php/{版本号}/cli/php.ini/etc/php/{版本号}/fpm/php.ini 与 /etc/php/{版本号}/fpm/pool.d/*.conf/etc/php/{版本号}/apache2/php.inisudo tar -czvf php-fpm-backup-$(date +%Y%m%d).tar.gz /etc/php/*/fpm/php-fpm.conf /etc/php/*/fpm/pool.d/ /etc/php/*/fpm/php.inisudo rsync -av --progress /etc/php /path/to/backup/php/sudo systemctl stop php{版本号}-fpmsudo tar -xzvf php-fpm-backup-YYYYMMDD.tar.gz -C /sudo rsync -av --progress /path/to/backup/php/ /etc/php/sudo systemctl start php{版本号}-fpm四 数据库备份恢复与运维要点
mysqldump -u root -p --single-transaction --routines --triggers --default-character-set=utf8mb4 your_db > backup.sqlmysql -u root -p your_db < backup.sqlSET FOREIGN_KEY_CHECKS=0;(导入后恢复为 1)。