Skip to main content

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
Archive Formats

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)

  1. Open Task Scheduler
  2. Click Create Basic Task
  3. Set trigger to Daily at your preferred time
  4. Action: Start a Program
    • Program: python.exe (full path)
    • Arguments: C:\path\to\backup_script.py

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

FunctionUse CasePreserves Metadata
shutil.copy()Quick file copyNo
shutil.copy2()Standard file backupYes
shutil.copytree()Full directory cloneYes
shutil.make_archive()Compressed archiveN/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.