How to Automate File Backups with Python
Data loss is inevitable, but losing work is optional. Automating your backups ensures you never forget to save critical files, whether it's project code, financial reports, or personal documents.
Python's built-in shutil module is the standard tool for high-level file operations. This guide covers backing up single files, entire directories, and compressing them into archives.
Backing Up a Single File
We use shutil.copy2() instead of copy() because it preserves metadata like creation and modification times. Adding a timestamp to the filename ensures we don't overwrite previous backups.
import shutil
import os
from datetime import datetime
def backup_file(source_file: str, backup_dir: str) -> None:
"""
Create a timestamped backup of a single file.
"""
# 1. Ensure backup folder exists
os.makedirs(backup_dir, exist_ok=True)
# 2. Generate timestamp (filesystem-safe format)
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
# 3. Build target filename
base_name = os.path.basename(source_file)
target_name = f"{timestamp}_{base_name}"
target_path = os.path.join(backup_dir, target_name)
# 4. Copy file with metadata
try:
shutil.copy2(source_file, target_path)
print(f"✅ Backup successful: {target_path}")
except FileNotFoundError:
print(f"❌ Error: Source file '{source_file}' not found.")
except PermissionError:
print("❌ Error: Permission denied.")
# Usage
backup_file("financial_report.xlsx", "backups")
Output:
✅ Backup successful: backups/2026-02-12_17-14-01_financial_report.xlsx
Backing Up an Entire Folder
To clone a directory including all subfolders and files, use shutil.copytree().
import shutil
from datetime import datetime
import os
def backup_folder(source_folder: str, backup_root: str) -> None:
"""
Create a timestamped backup of an entire directory.
"""
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
folder_name = os.path.basename(source_folder.rstrip('/\\'))
target_dir = os.path.join(backup_root, f"{folder_name}_{timestamp}")
try:
shutil.copytree(source_folder, target_dir)
print(f"✅ Folder backup created: {target_dir}")
except FileExistsError:
print("❌ Error: Backup folder already exists.")
except FileNotFoundError:
print(f"❌ Error: Source folder '{source_folder}' not found.")
except Exception as e:
print(f"❌ Error: {e}")
# Usage
backup_folder("my_project", "backups")
Output:
✅ Folder backup created: backups/my_project_2026-01-15_14-30-00
Creating ZIP Archives
Copying raw files consumes significant disk space. Compressing backups into .zip archives is more efficient and easier to manage.
import shutil
from datetime import datetime
import os
def backup_to_zip(source_folder: str, backup_dir: str) -> None:
"""
Create a compressed ZIP backup of a folder.
"""
os.makedirs(backup_dir, exist_ok=True)
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
folder_name = os.path.basename(source_folder.rstrip('/\\'))
archive_name = os.path.join(backup_dir, f"{folder_name}_{timestamp}")
# Creates archive_name.zip automatically
shutil.make_archive(archive_name, 'zip', source_folder)
print(f"📦 Archive created: {archive_name}.zip")
# Usage
backup_to_zip("my_project", "backups")
Output:
📦 Archive created: backups/my_project_2026-01-15_14-30-00.zip
shutil.make_archive() supports multiple formats:
'zip'– Universal, works everywhere'tar'– Unix standard, no compression'gztar'– Gzip compressed tar (smaller files)'bztar'– Bzip2 compressed tar (smallest, slower)
Complete Backup Script
Here's a production-ready script that combines all techniques with configuration and logging:
import shutil
import os
from datetime import datetime
from pathlib import Path
class BackupManager:
"""Manages file and folder backups with compression."""
def __init__(self, backup_root: str = "backups"):
self.backup_root = Path(backup_root)
self.backup_root.mkdir(parents=True, exist_ok=True)
def _get_timestamp(self) -> str:
return datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
def backup_file(self, source: str) -> Path | None:
"""Backup a single file with timestamp."""
source_path = Path(source)
if not source_path.exists():
print(f"❌ Source not found: {source}")
return None
target = self.backup_root / f"{self._get_timestamp()}_{source_path.name}"
try:
shutil.copy2(source_path, target)
print(f"✅ File backed up: {target}")
return target
except Exception as e:
print(f"❌ Backup failed: {e}")
return None
def backup_folder(self, source: str, compress: bool = True) -> Path | None:
"""Backup a folder, optionally compressed."""
source_path = Path(source)
if not source_path.exists():
print(f"❌ Source not found: {source}")
return None
timestamp = self._get_timestamp()
base_name = f"{source_path.name}_{timestamp}"
try:
if compress:
archive_path = self.backup_root / base_name
shutil.make_archive(str(archive_path), 'zip', source_path)
result = archive_path.with_suffix('.zip')
print(f"📦 Compressed backup: {result}")
else:
target = self.backup_root / base_name
shutil.copytree(source_path, target)
result = target
print(f"✅ Folder backed up: {result}")
return result
except Exception as e:
print(f"❌ Backup failed: {e}")
return None
# Usage
if __name__ == "__main__":
manager = BackupManager("daily_backups")
# Backup individual files
manager.backup_file("config.yaml")
manager.backup_file("database.db")
# Backup folders
manager.backup_folder("src", compress=True)
manager.backup_folder("docs", compress=False)
Scheduling Automatic Backups
A backup script is only useful if it runs automatically.
Windows (Task Scheduler)
- Open Task Scheduler
- Click Create Basic Task
- Set trigger to Daily at your preferred time
- Action: Start a Program
- Program:
python.exe(full path) - Arguments:
C:\path\to\backup_script.py
- Program:
Linux / macOS (Cron)
Open your crontab:
crontab -e
Add a scheduled job:
# Run backup every day at 5 PM
0 17 * * * /usr/bin/python3 /home/user/scripts/backup.py >> /var/log/backup.log 2>&1
# Run backup every Sunday at midnight
0 0 * * 0 /usr/bin/python3 /home/user/scripts/backup.py
Function Reference
| Function | Use Case | Preserves Metadata |
|---|---|---|
shutil.copy() | Quick file copy | No |
shutil.copy2() | Standard file backup | Yes |
shutil.copytree() | Full directory clone | Yes |
shutil.make_archive() | Compressed archive | N/A |
Summary
- Use
shutil.copy2()for single files to preserve timestamps. - Use
shutil.copytree()to clone entire directories. - Use
shutil.make_archive()for compressed backups that save disk space. - Always add timestamps to backup names to maintain version history.
- Schedule your script with cron or Task Scheduler for true automation.