You can set up automated backups for your small business in about 30 minutes using a free Python script that copies business-critical files to multiple locations — local storage, external drives, and cloud — verifies integrity, and emails you a confirmation report on schedule. Following the 3-2-1 rule (three copies, two media types, one offsite), the script removes humans from the loop entirely. For Volusia County businesses facing annual hurricane risk, verified cloud automated backups ensure data survival even if physical locations are damaged.
You know you should be backing up your business data. You have probably been meaning to set it up for months. Maybe years. And every time you think about it, you picture expensive software with monthly subscriptions, complicated setup processes, and IT consultants billing you by the hour.
An automated backup script runs on a schedule without any human involvement, copying your business-critical files to multiple locations — local storage, external drives, and cloud — then sending you an email confirming everything worked. The script I am giving you in this article does all of that. It is free. It runs on Python (which is also free). And it takes about 30 minutes to set up.
No paid software. No monthly subscription. No vendor lock-in. Just a Python script, a schedule, and the peace of mind that comes from knowing your data is protected.
Why Most Small Businesses Still Do Not Back Up Properly
Before I hand you the script, let me explain why this matters, because understanding the “why” makes you more likely to actually set it up.
I work with small businesses across Daytona Beach and Volusia County, and the backup situation I see most often is one of these:
- The “I’ll do it tomorrow” plan: No backups at all. The owner knows they should, but it never becomes urgent until it is too late.
- The manual USB drive: Someone plugs in a drive once a week and copies files manually. They forget half the time, and the drive sits right next to the computer it is backing up (so a fire or flood destroys both).
- The paid service nobody checks: A cloud backup subscription that was set up years ago. Nobody has verified it is still running, still backing up the right directories, or still accessible.
All three of these fail under pressure. The first one fails completely. The second fails because it depends on a human remembering to do it and doing it correctly every time. The third fails because untested backups are not backups — they are assumptions.
Automated backups solve the core problem: they remove the human from the loop. The script runs on a schedule, does the same thing every time, verifies its own work, and tells you whether it succeeded. You do not have to remember. You do not have to click anything. You just get an email in the morning that says “Backup successful: 847 files, 2.3 GB.”
What This Script Does
Here is the complete feature list before I show you the code:
- Copies your specified directories to a local backup folder with timestamped names
- Compresses everything into a ZIP file to save storage space
- Generates SHA-256 checksums for every file so you can verify integrity later
- Syncs the backup to Azure Blob Storage or AWS S3 (optional — skip this if you do not use cloud)
- Cleans up old backups automatically, keeping daily backups for 30 days and weekly backups for 12 weeks
- Sends an email report with the backup status, file count, size, and any errors
- Logs everything to a file so you can troubleshoot if something goes wrong
No pip installs. No external packages. Everything uses Python’s built-in standard library.
The Complete Script
Save this as backup.py wherever you keep your scripts. On Windows, something like C:Scriptsbackup.py. On Linux or Mac, /opt/scripts/backup.py.
!/usr/bin/env python3
"""
Automated Backup Script for Small Businesses
Features: Local backup, cloud sync (Azure/AWS), email notification, scheduling
Requires: Python 3.8+, no pip packages
Optional: Azure CLI or AWS CLI for cloud sync
"""
from datetime import datetime, timedelta
from pathlib import Path
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
=== CONFIGURATION - Edit these for your business ===
CONFIG = {
# What to back up
"source_dirs": [
"C:/BusinessData",
"C:/QuickBooks/Data",
],
# Where to store backups
"backup_dir": "D:/Backups",
# Cloud sync (set to None to skip)
"cloud_provider": "azure", # "azure", "aws", or None
"cloud_destination": "businessbackups/daily",
# Email notifications
"smtp_server": "smtp.office365.com",
"smtp_port": 587,
"email_from": "[email protected]",
"email_to": "[email protected]",
"email_password_env": "BACKUP_EMAIL_PASSWORD",
# Retention
"keep_days": 30,
"keep_weekly": 12, # weeks
# Logging
"log_file": "D:/Backups/backup.log",
}
Set up logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler(CONFIG["log_file"], encoding="utf-8"),
logging.StreamHandler(),
],
)
log = logging.getLogger("backup")
def get_backup_name():
"""Generate timestamped backup name."""
ts = datetime.now().strftime("%Y%m%d-%H%M%S")
dow = datetime.now().strftime("%A")
return f"backup-{ts}-{dow}"
def calculate_sha256(filepath):
"""SHA-256 checksum for file integrity verification."""
sha = hashlib.sha256()
with open(filepath, "rb") as f:
while chunk := f.read(65536):
sha.update(chunk)
return sha.hexdigest()
def create_backup(source_dirs, backup_dir, backup_name):
"""Create compressed backup with integrity manifest."""
backup_path = Path(backup_dir) / backup_name
zip_path = Path(backup_dir) / f"{backup_name}.zip"
backup_path.mkdir(parents=True, exist_ok=True)
manifest = {"timestamp": datetime.now().isoformat(), "files": {}}
total_size = 0
for src in source_dirs:
src_path = Path(src)
if not src_path.exists():
log.warning(f"Source not found: {src}")
continue
dest = backup_path / src_path.name
log.info(f"Copying: {src} -> {dest}")
shutil.copytree(str(src_path), str(dest), dirs_exist_ok=True)
for fpath in dest.rglob("*"):
if fpath.is_file():
rel = str(fpath.relative_to(backup_path))
manifest["files"][rel] = calculate_sha256(str(fpath))
total_size += fpath.stat().st_size
# Save manifest
manifest_file = backup_path / "manifest.json"
with open(manifest_file, "w") as f:
json.dump(manifest, f, indent=2)
# Compress
log.info(f"Compressing to {zip_path}...")
with zipfile.ZipFile(str(zip_path), "w", zipfile.ZIP_DEFLATED) as zf:
for fpath in backup_path.rglob("*"):
if fpath.is_file():
zf.write(str(fpath), str(fpath.relative_to(backup_path)))
# Clean up uncompressed copy
shutil.rmtree(str(backup_path))
zip_size = zip_path.stat().st_size
file_count = len(manifest["files"])
log.info(f"Backup created: {file_count} files, "
f"{total_size / 1048576:.1f} MB -> {zip_size / 1048576:.1f} MB compressed")
return zip_path, manifest, total_size, zip_size
def sync_to_cloud(zip_path, provider, destination):
“””Upload backup to cloud storage.”””
if provider is None:
log.info(“Cloud sync disabled, skipping”)
return True
filename = zip_path.name
if provider == "azure":
container = destination.split("/")[0]
blob_path = destination.split("/", 1)[1] + "/" + filename
cmd = f'az storage blob upload --file "{zip_path}" --container-name "{container}" --name "{blob_path}" --overwrite'
elif provider == "aws":
cmd = f'aws s3 cp "{zip_path}" "s3://{destination}/{filename}"'
else:
log.error(f"Unknown provider: {provider}")
return False
log.info(f"Cloud sync: {provider}")
result = os.system(cmd)
if result == 0:
log.info("Cloud sync: SUCCESS")
return True
else:
log.error(f"Cloud sync: FAILED (exit code {result})")
return False
def cleanup_old_backups(backup_dir, keep_days):
“””Remove old backups, preserve Sunday backups for weekly retention.”””
cutoff = datetime.now() – timedelta(days=keep_days)
removed = 0
for item in sorted(Path(backup_dir).glob("backup-*.zip")):
try:
date_str = item.stem.split("-")[1]
backup_date = datetime.strptime(date_str, "%Y%m%d")
if backup_date < cutoff:
if "Sunday" not in item.stem:
item.unlink()
removed += 1
log.info(f"Removed: {item.name}")
except (ValueError, IndexError):
continue
log.info(f"Cleanup: {removed} old backups removed")
return removed
def send_email_report(config, results):
“””Send backup status email.”””
password = os.environ.get(config[“email_password_env”])
if not password:
log.warning(“Email password not set, skipping notification”)
return False
status = "SUCCESS" if results["success"] else "FAILED"
subject = f"[Backup {status}] {results['backup_name']}"
body = f"""Backup Report - {results['backup_name']}
{‘=’50}
Status: {status}
Files: {results.get(‘file_count’, ‘N/A’)}
Size: {results.get(‘original_mb’, ‘N/A’)} MB -> {results.get(‘compressed_mb’, ‘N/A’)} MB
Cloud Sync: {‘OK’ if results.get(‘cloud_ok’) else ‘FAILED or SKIPPED’}
Cleaned: {results.get(‘removed’, 0)} old backups
Duration: {results.get(‘duration’, ‘N/A’)} seconds
{‘=’50}
“””
try:
msg = MIMEMultipart()
msg["From"] = config["email_from"]
msg["To"] = config["email_to"]
msg["Subject"] = subject
msg.attach(MIMEText(body, "plain"))
with smtplib.SMTP(config["smtp_server"], config["smtp_port"]) as server:
server.starttls()
server.login(config["email_from"], password)
server.send_message(msg)
log.info(f"Email sent to {config['email_to']}")
return True
except Exception as e:
log.error(f"Email failed: {e}")
return False
def run():
“””Main backup routine.”””
start = datetime.now()
backup_name = get_backup_name()
log.info(f"{'='*50}")
log.info(f"BACKUP START: {backup_name}")
log.info(f"{'='*50}")
results = {"backup_name": backup_name, "success": False}
try:
log.info("[1/3] Creating backup...")
zip_path, manifest, orig_size, zip_size = create_backup(
CONFIG["source_dirs"], CONFIG["backup_dir"], backup_name
)
results["file_count"] = len(manifest["files"])
results["original_mb"] = f"{orig_size / 1048576:.1f}"
results["compressed_mb"] = f"{zip_size / 1048576:.1f}"
log.info("[2/3] Syncing to cloud...")
results["cloud_ok"] = sync_to_cloud(
zip_path, CONFIG["cloud_provider"], CONFIG["cloud_destination"]
)
log.info("[3/3] Cleaning old backups...")
results["removed"] = cleanup_old_backups(
CONFIG["backup_dir"], CONFIG["keep_days"]
)
results["success"] = True
except Exception as e:
log.error(f"Backup FAILED: {e}")
results["error"] = str(e)
duration = (datetime.now() - start).total_seconds()
results["duration"] = f"{duration:.1f}"
log.info(f"BACKUP {'SUCCESS' if results['success'] else 'FAILED'} in {duration:.1f}s")
send_email_report(CONFIG, results)
return results
if name == “main“:
run()
That is the whole thing. Let me walk you through how to get it running.</p>
<h2>Step 1: Install Python (If You Have Not Already)</h2>
<p>If you are on Windows, download Python from <a href="https://www.python.org/downloads/">python.org</a>. During installation, make sure you check the box that says <strong>"Add Python to PATH."</strong> This is the single most common mistake people make, and it causes the script to fail with a "python not found" error.</p>
<p>On Mac, Python 3 comes preinstalled on recent versions. On Linux, it is almost certainly already there. You can check by opening a terminal and typing:</p>
<p>
bash
python3 –version
output: Python 3.12.x (or similar)
``text:=`) that was introduced in Python 3.8. If you are running an older version, update it.
You need version 3.8 or higher. The script uses a feature called the walrus operator (
Step 2: Configure the Script
Open backup.py in any text editor (Notepad works, but VS Code or Notepad++ are better if you have them). The CONFIG dictionary at the top is the only part you need to change.
source_dirs — Change these to the actual directories you want to back up. For a typical small business in Daytona Beach, this might be:
pythontext
"source_dirs": [
"C:/Users/Shared/BusinessDocs",
"C:/ProgramData/QuickBooks",
"C:/Users/Admin/Desktop/ClientFiles",
],
Add as many directories as you need. The script backs up all of them in a single run.
backup_dir — Where the compressed backup files are stored. I recommend a separate drive if you have one. If your business data is on C:, store backups on D:. This protects you against a single drive failure.
cloud_provider — Set this to "azure" if you use Azure, "aws" if you use AWS, or None (no quotes) if you do not want cloud sync. Cloud sync requires the Azure CLI or AWS CLI to be installed on the machine, but the script works fine without it — you just will not have the offsite copy automated.
smtp_server and smtp_port — If your business uses Microsoft 365 (which most businesses in Volusia County do), leave these as smtp.office365.com and 587. For Gmail, use smtp.gmail.com and 587. For other providers, check their SMTP settings.
email_password_env — This is the name of an environment variable that holds your email password. We do not put passwords directly in scripts because that is a security risk. I will show you how to set this up in Step 4.
keep_days — How many days to retain daily backups. Thirty days is a reasonable default. After 30 days, daily backups are deleted, but Sunday backups are preserved for the keep_weekly period (12 weeks by default). This gives you a good balance between storage usage and recovery options.
Step 3: Run It Once Manually
Before you schedule anything, run the script once by hand to make sure it works. Open a terminal or command prompt, navigate to where you saved the script, and run:
bashtext
python3 backup.py
You should see output like this:
texttext
2026-03-20 14:30:01 [INFO] ==================================================
2026-03-20 14:30:01 [INFO] BACKUP START: backup-20260320-143001-Thursday
2026-03-20 14:30:01 [INFO] ==================================================
2026-03-20 14:30:01 [INFO] [1/3] Creating backup...
2026-03-20 14:30:01 [INFO] Copying: C:/BusinessData -> D:/Backups/backup-20260320-143001-Thursday/BusinessData
2026-03-20 14:30:15 [INFO] Compressing to D:/Backups/backup-20260320-143001-Thursday.zip...
2026-03-20 14:30:22 [INFO] Backup created: 847 files, 2340.5 MB -> 1205.3 MB compressed
2026-03-20 14:30:22 [INFO] [2/3] Syncing to cloud...
2026-03-20 14:30:22 [INFO] Cloud sync disabled, skipping
2026-03-20 14:30:22 [INFO] [3/3] Cleaning old backups...
2026-03-20 14:30:22 [INFO] Cleanup: 0 old backups removed
2026-03-20 14:30:22 [INFO] BACKUP SUCCESS in 21.3s
If you see errors, the log will tell you exactly what went wrong. The most common issues are:
- Source directory does not exist: Double-check the paths in
source_dirs. Use forward slashes even on Windows (C:/BusinessData, notC:BusinessData). - Permission denied: Run the script as administrator, or make sure the user running it has read access to the source directories and write access to the backup directory.
- Disk full: Check that the backup drive has enough free space for the compressed backup.
Step 4: Set Up Email Notifications
The email feature requires you to store your email password in an environment variable. This is safer than putting it in the script file.
On Windows (run in Command Prompt as administrator):
cmdtext
setx BACKUP_EMAIL_PASSWORD "your-email-password-here" /M
On Linux/Mac (add to your .bashrc or .profile):
bashtext
export BACKUP_EMAIL_PASSWORD="your-email-password-here"
If you are using Microsoft 365 with multi-factor authentication (which you should be), you will need to create an app password instead of using your regular password. Go to your Microsoft account security settings, find “App passwords,” and generate one specifically for the backup script.
After setting the environment variable, run the script again. This time you should get an email with the backup report. If the email fails, the backup itself still completes — the email notification is a nice-to-have, not a dependency.
Step 5: Schedule It to Run Automatically
This is the part that makes it truly “set and forget.” You want the backup running every night at 2 AM (or whenever your business is least active).
Windows: Task Scheduler
Open PowerShell as administrator and run:
``powershell
$action = New-ScheduledTaskAction
-Execute “python3” `
-Argument “C:Scriptsbackup.py”
$trigger = New-ScheduledTaskTrigger `
-Daily -At “2:00AM”
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable
-DontStopOnIdleEnd -RestartCount 3
-RestartInterval (New-TimeSpan -Minutes 5)
Register-ScheduledTask -TaskName "BusinessBackup"
-Action $action -Trigger $trigger
-Settings $settings -Description "Automated daily backup with cloud sync"
-RunLevel Highest
``text-StartWhenAvailable`** flag is important. If the computer is off at 2 AM (maybe there was a power outage), the task will run as soon as the computer comes back online. Without this flag, the backup would just be skipped.
The **
The -RestartCount 3 flag tells Task Scheduler to retry up to three times if the backup fails, waiting five minutes between attempts. This handles temporary issues like a network drive being briefly unavailable.
Linux: Cron
Open your crontab editor:
bashtext
crontab -e
Add this line:
bashtext
0 2 * * * /usr/bin/python3 /opt/scripts/backup.py >> /var/log/backup-cron.log 2>&1
That is it. The backup will run every night at 2 AM. The >> /var/log/backup-cron.log 2>&1 part captures both the normal output and any errors into a log file you can review if something seems off.
Step 6: Set Up Cloud Sync (Optional but Recommended)
If you want the offsite copy (and you should, especially if you are in hurricane-prone areas like Port Orange or anywhere along the Florida coast), you need the Azure CLI or AWS CLI installed.
For Azure (Most Volusia County Businesses)
Install the Azure CLI:
Windows (run in PowerShell as admin)
winget install -e --id Microsoft.AzureCLI
Mac
brew install azure-cli
Linux
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
Log in and create a storage container:</p>
<p><code>bash
az login
az storage account create --name yourbizbackups --resource-group backup-rg --location eastus --sku Standard_LRS
az storage container create --name businessbackups --account-name yourbizbackups</code>text
Now update <strong><code>cloud_provider</code></strong> to <code>"azure"</code> and <strong><code>cloud_destination</code></strong> to <code>"businessbackups/daily"</code> in the script config. The next time the backup runs, it will automatically upload to Azure.</p>
<p>Azure Blob Storage costs about $0.018 per GB per month for the hot tier. For a typical 50 GB of business data, that is less than $1 per month. The cool tier is even cheaper at $0.01 per GB per month if you do not need to access your backups frequently (which, ideally, you do not).</p>
<h3>For AWS</h3>
<p>Install the AWS CLI and configure it:</p>
<p>
bash
aws configure
Enter your AWS Access Key, Secret Key, region, and output format
Create an S3 bucket:
bash
aws s3 mb s3://yourbiz-backups-2026text
Set cloud_provider to "aws" and cloud_destination to "yourbiz-backups-2026/daily".
How to Verify Your Backups Are Working
Setting up the backup is step one. Verifying it works every month is step two, and it is the step most people skip. I covered this in detail in the disaster recovery post, including a free recovery test script.
Here is the quick version: once a month, open one of your backup ZIP files. Extract it. Open a few files. Make sure they are current and not corrupted. Check the manifest.json file inside the backup -- it lists every file and its SHA-256 checksum. If you want to be thorough, verify a few checksums match.
This takes five minutes. Put it on your calendar for the first Monday of every month.
What This Script Does NOT Cover
I want to be honest about the limitations so you know when you have outgrown the DIY approach:
Database backups: If you run databases (SQL Server, PostgreSQL, MySQL), you need to export them to files before this script runs. A file-level copy of a running database can result in a corrupted backup. Use pg_dump for PostgreSQL, mysqldump for MySQL, or the built-in backup tool for SQL Server. Then point this script at the dump directory.
Application state: Some applications (like QuickBooks Desktop) lock their data files while running. The backup script cannot copy locked files. Either close the application before the backup runs (the 2 AM schedule helps with this) or use the application's built-in export feature first.
Real-time protection: This script runs on a schedule. If your server crashes at 11 PM and your last backup was at 2 AM, you lose up to 21 hours of data. If that RPO is too wide, you need more frequent backups or a different solution. For most small businesses in Daytona Beach and Ormond Beach, nightly backups are sufficient. But if your business generates high-value transactions throughout the day, consider running the backup every 4-6 hours instead.
Encryption: The backup ZIP files are not encrypted. If your backups contain sensitive data (patient records, financial information), you should add encryption. The simplest approach is to use 7-Zip with AES-256 encryption instead of the built-in zipfile module, or encrypt the entire backup drive using BitLocker (Windows) or LUKS (Linux).
The Custom-Built Advantage
This script handles the basics well. For a solopreneur or a business with straightforward data needs, it might be everything you need. But as your infrastructure grows, the gaps in DIY backup become apparent.
When we set up backup systems for businesses across Daytona Beach, Port Orange, and throughout Volusia County, we build solutions that address the full picture:
Application-aware backups that handle databases, QuickBooks, and other locked-file scenarios correctly
Encrypted backups that meet HIPAA, PCI, and other compliance requirements
Monitored backup systems where we are alerted if a backup fails, not just the business owner
Tested recovery procedures with documented RTO/RPO verification
Multi-site replication for businesses that need near-zero data loss
If you are ready to move beyond the DIY approach, our cloud migration and backup services include professional backup implementation. We will audit your current data landscape, configure backups that cover everything (including the tricky database and application state issues), and test the recovery process so you know it works before you need it. Businesses across Daytona Beach are already running on systems we built.
Frequently Asked Questions
How do I automate backups for my small business for free?
Install Python 3.8 or later, save the backup script from this article, configure the source directories and backup location in the CONFIG section, run it once manually to verify, then schedule it using Windows Task Scheduler or Linux cron. The entire setup takes about 30 minutes and uses only free, open-source tools.
What is the best free backup script for small business?
A Python script using the standard library is the most portable and maintainable option. The script in this article handles compressed backups with SHA-256 integrity verification, optional cloud sync to Azure or AWS, email notifications, and automatic retention cleanup -- all without any paid packages or subscriptions.
How often should I run automated backups?
Daily backups at 2 AM are the standard for most small businesses. If your business generates high-value data throughout the day (financial transactions, medical records), consider running backups every 4-6 hours. The key metric is your Recovery Point Objective (RPO) -- how much data you can afford to lose.
Do I need cloud storage for my backups?
Cloud storage is strongly recommended for offsite protection. If a fire, flood, or hurricane destroys your office, local backups are destroyed too. Azure Blob Storage costs less than $1 per month for 50 GB. It is cheap insurance against physical disasters -- especially relevant for businesses in hurricane-prone areas like Florida.
Can this script back up QuickBooks data?
Yes, with a caveat. QuickBooks Desktop locks its data files while running. Schedule the backup for a time when QuickBooks is closed (2 AM works well). For QuickBooks Online, your data is already in the cloud, but you should still export and back up reports and transaction data regularly.
What happens if the backup script fails?
The script logs all errors to a file and sends an email notification on failure (if email is configured). Common failure causes are: source directory not found, destination disk full, or cloud sync credentials expired. Check the log file at the path specified in CONFIG for detailed error messages.
What to Do Right Now
Download Python from python.org if you do not have it. Remember: check "Add to PATH."
Save the script as backup.py and edit the CONFIG section for your directories.
Run it once manually with python3 backup.py and verify the output.
Schedule it with Task Scheduler (Windows) or cron (Linux) to run at 2 AM nightly.
Set up cloud sync when you are ready for offsite protection.
Test monthly. Open a backup, verify files, check the manifest.
The whole setup takes 30 minutes. The peace of mind lasts indefinitely.
For a deeper look at the strategy behind your backups, read our disaster recovery guide. And if you need help building infrastructure as code that makes your entire setup reproducible, we cover that too.