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.
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 cleanup1. Install AWS CLI on Ubuntu
Install via Snap (easiest, auto-updates):
sudo snap install aws-cli --classicVerify:
aws --version2. Create an IAM User (S3 access only)
In the AWS Console:
- Go to IAM → Users → Create User
- Enable programmatic access
- 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 configureEnter:
Access Key
Secret Key
Region (e.g. ap-south-1)
Output format: json4. Create the backup script
nano ~/mongo-backup.shPaste:
#!/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.awsinstead of justaws? Cron runs commands with a minimal PATH and no login shell.mongodumplives where cron already looks; the Snap-installedawsbinary does not. Rather than reshape cron's PATH just to run one binary,snap runis Snap's own dispatcher — it works no matter what PATH looks like. The verbose name issnap run aws-cli.awsbecause the snap package isaws-cliand its app is registered asaws; the bareawsyou type interactively is a snap-managed auto-alias, and that alias isn't guaranteed to resolve insidesnap runin scripted/cron contexts. Invoking the fully qualifiedaws-cli.awsbypasses the alias and always works. If you install AWS CLI viapipor the official installer instead, drop the whole prefix and just callaws.
Make it executable and run once manually to confirm everything wires up:
chmod +x ~/mongo-backup.sh
~/mongo-backup.sh5. Set up the cron job (2 AM IST)
If your server is on UTC, 2 AM IST = 20:30 UTC the previous day.
crontab -eAdd:
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.)
6. Add an S3 lifecycle rule (highly recommended)
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.