SN

Automated MongoDB Backup to AWS S3 on Ubuntu with Cron

Step-by-step guide to scheduling daily MongoDB backups, uploading them to AWS S3, and managing local retention — production-ready and battle-tested.

5 min readUpdated August 24, 2026

Running MongoDB in production without an automated, off-site backup is asking for a very bad day. This guide walks through the exact setup I've been running on Ubuntu servers: mongodump → gzip archive → AWS S3, on a daily cron, with local retention cleanup.

Goal

  • Automated MongoDB backup
  • Upload to AWS S3
  • Run daily at 2 AM IST
  • Retain only the last 7 days locally
  • Production-safe cron execution

Architecture

MongoDB

mongodump (gzip archive)

AWS S3 upload

Local retention cleanup

1. Install AWS CLI on Ubuntu

Install via Snap (easiest, auto-updates):

sudo snap install aws-cli --classic

Verify:

aws --version

2. Create an IAM User (S3 access only)

In the AWS Console:

  1. Go to IAM → Users → Create User
  2. Enable programmatic access
  3. Attach a policy

For quick setup you can use the managed policy AmazonS3FullAccess, but for production use a scoped policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::your-bucket-name",
        "arn:aws:s3:::your-bucket-name/*"
      ]
    }
  ]
}

Save the Access Key and Secret Key — you'll need them in the next step.

3. Configure AWS on the server

aws configure

Enter:

Access Key
Secret Key
Region (e.g. ap-south-1)
Output format: json

4. Create the backup script

nano ~/mongo-backup.sh

Paste:

#!/bin/bash
# === MongoDB Daily Backup Script ===
 
set -e
 
MONGO_URI="mongodb://localhost:27017/mydatabase"
BACKUP_DIR="$HOME/bkp"
DATE=$(date +"%Y-%m-%d_%H-%M-%S")
S3_BUCKET="s3://your-bucket-name"
BACKUP_NAME="backup-$DATE.gz"
RETENTION_DAYS=7
LOG_FILE="$HOME/mongo-backup.log"
 
mkdir -p "$BACKUP_DIR"
 
echo "Starting backup: $DATE" >> "$LOG_FILE"
 
# Dump database (mongodump ships in cron's default PATH)
mongodump --uri="$MONGO_URI" \
  --archive="$BACKUP_DIR/$BACKUP_NAME" --gzip >> "$LOG_FILE" 2>&1
 
# Upload to S3 via Snap's canonical app name (no PATH gymnastics required)
snap run aws-cli.aws s3 cp "$BACKUP_DIR/$BACKUP_NAME" "$S3_BUCKET/" >> "$LOG_FILE" 2>&1
 
# Cleanup old local backups
find "$BACKUP_DIR" -type f -mtime +$RETENTION_DAYS -delete
 
echo "Backup completed successfully: $BACKUP_NAME" >> "$LOG_FILE"

Why snap run aws-cli.aws instead of just aws? Cron runs commands with a minimal PATH and no login shell. mongodump lives where cron already looks; the Snap-installed aws binary does not. Rather than reshape cron's PATH just to run one binary, snap run is Snap's own dispatcher — it works no matter what PATH looks like. The verbose name is snap run aws-cli.aws because the snap package is aws-cli and its app is registered as aws; the bare aws you type interactively is a snap-managed auto-alias, and that alias isn't guaranteed to resolve inside snap run in scripted/cron contexts. Invoking the fully qualified aws-cli.aws bypasses the alias and always works. If you install AWS CLI via pip or the official installer instead, drop the whole prefix and just call aws.

Make it executable and run once manually to confirm everything wires up:

chmod +x ~/mongo-backup.sh
~/mongo-backup.sh

5. Set up the cron job (2 AM IST)

If your server is on UTC, 2 AM IST = 20:30 UTC the previous day.

crontab -e

Add:

30 20 * * * $HOME/mongo-backup.sh >> $HOME/mongo-backup.log 2>&1

(Cron runs the command through a POSIX shell that expands $HOME — so you don't need to hard-code your user's home directory here either.)

In the S3 console: Bucket → Management → Lifecycle Rules → Create rule. Delete objects after 30 days. This stops storage from growing forever.

Production recommendations

  • Use an IAM role instead of access keys if running on EC2
  • Restrict S3 permissions to a specific bucket
  • Enable S3 versioning
  • Enable server-side encryption (SSE-S3 or SSE-KMS)
  • Periodically test the restore — a backup you've never restored is not a backup

FAQ

Does mongodump need to stop MongoDB first? No. mongodump reads from a live standalone or replica set without downtime. On a replica set it hits the primary by default — pass --readPreference=secondary if you'd rather offload the I/O to a follower.

How big will my backups get in S3? --gzip typically compresses BSON dumps to 15–30% of the raw database size. A 10 GB database becomes a ~2 GB archive. With a 30-day S3 lifecycle rule and a busy database you're storing roughly 60 GB at any time — check the S3 pricing page for your region before assuming that's cheap, since regional rates move.

Why not mongoexport instead? mongoexport writes JSON and loses BSON types (dates, ObjectIds, Decimal128, binary data). Use it for one-off data pulls, never for backups.

What if I run MongoDB in Docker or a managed service like Atlas? The script works unchanged for Atlas — just point MONGO_URI at the SRV connection string. For local Docker, either docker exec into the container to run mongodump, or expose the port and dump from the host.

How do I restore from one of these archives? mongorestore --gzip --archive=backup-2026-08-18_02-00-00.gz. Do this on a scratch database at least once a quarter — a backup you've never restored is not a backup.

What's next

Backups without monitoring are risky. If mongodump or the S3 upload silently fails, you only find out the day you need the backup. In the next post I wire Amazon SES failure alerts into this exact script so a broken backup pages me by email.

And a backup you've never restored is just a hope — the third post restores one of these archives from S3 into a scratch database and proves it works before you actually need it.