ESC

Start typing to search across invoices, services, domains, tickets, and more...

Search... Ctrl+K
Linux Server

How to Back Up a Linux Server: rsync, tar + cron, MySQL/MariaDB Dumps and restic to S3

5 steps 16 min read 5 views 0
On this page

Disk failure, an accidental rm or a ransomware attack can wipe data in seconds, so a reliable Linux server backup is non-negotiable. This guide combines four complementary methods: rsync to another server, tar archives on a cron schedule, rotated MySQL/MariaDB dumps and encrypted restic backups to S3-compatible storage, followed by restore tests. Commands work on Debian/Ubuntu and Rocky Linux/AlmaLinux.

Follow the 3-2-1 rule: three copies of your data, on two kinds of media, one of them off-site. Backups on the same disk do not survive a disk failure or a full compromise. If you need a second server or a larger data disk for backups, contact our support team to confirm the configuration.

Step 1: Back Up to Another Server with rsync

rsync transfers only what changed, which makes it ideal for website directories and configuration. On the backup host create a regular user named backup and the directory /backup/web01; on the source server create a dedicated SSH key (see our SSH key tutorial). --delete removes files on the target that no longer exist on the source, so do a dry run with -n first.

# Install rsync on BOTH servers
apt install -y rsync        # Debian / Ubuntu
dnf install -y rsync        # Rocky Linux / AlmaLinux

# On the source server: dedicated key for backups
ssh-keygen -t ed25519 -f /root/.ssh/backup_key -N ""
ssh-copy-id -i /root/.ssh/backup_key.pub -p 22 [email protected]

# Preview first (-n = dry run), then run for real
rsync -aHn --delete -e "ssh -i /root/.ssh/backup_key -p 22" \
  /etc /var/www [email protected]:/backup/web01/
rsync -aH --delete -e "ssh -i /root/.ssh/backup_key -p 22" \
  /etc /var/www [email protected]:/backup/web01/

Step 2: Scheduled Archives with tar and cron

rsync creates a mirror: if you delete something on the source, the next sync deletes it on the backup too. Complement it with a dated tar archive every day and keep the last seven. The script prunes old archives with find -mtime, and cron runs it every night.

cat > /usr/local/bin/backup-files.sh <<'EOF'
#!/bin/bash
set -euo pipefail
DEST=/backup/files
KEEP_DAYS=7
mkdir -p "$DEST"
# tar exit code 1 = "file changed while reading", which is acceptable
tar -czpf "$DEST/files-$(date +%F).tar.gz" /etc /var/www /root || [ $? -eq 1 ]
find "$DEST" -name 'files-*.tar.gz' -mtime +$KEEP_DAYS -delete
EOF
chmod 700 /usr/local/bin/backup-files.sh
/usr/local/bin/backup-files.sh && ls -lh /backup/files

# Schedule: crontab -e
10 3 * * * /usr/local/bin/backup-files.sh >> /var/log/backup.log 2>&1

Step 3: Automatic MySQL/MariaDB Backups with Rotation

Copying the live database files often produces a corrupt backup; export with mysqldump or mariadb-dump instead. --single-transaction takes a consistent InnoDB snapshot without locking tables. The script dumps each database separately, compresses it with gzip and keeps 14 days. Credentials live in /root/.my.cnf with mode 600 so the password never appears on the command line or in the process list.

# Credentials (skip if root can log in to MariaDB/MySQL via unix_socket)
cat > /root/.my.cnf <<'EOF'
[client]
user=root
password=YourDatabaseRootPassword
EOF
chmod 600 /root/.my.cnf

cat > /usr/local/bin/backup-db.sh <<'EOF'
#!/bin/bash
set -euo pipefail
DEST=/backup/mysql
KEEP_DAYS=14
mkdir -p "$DEST"
CLIENT=$(command -v mariadb || command -v mysql)
DUMP=$(command -v mariadb-dump || command -v mysqldump)
for DB in $("$CLIENT" -N -e "SHOW DATABASES" | grep -Ev '^(information_schema|performance_schema|sys)$'); do
  "$DUMP" --single-transaction --routines --triggers --events "$DB" \
    | gzip > "$DEST/$DB-$(date +%F_%H%M).sql.gz"
done
find "$DEST" -name '*.sql.gz' -mtime +$KEEP_DAYS -delete
EOF
chmod 700 /usr/local/bin/backup-db.sh

# Schedule: crontab -e
20 2 * * * /usr/local/bin/backup-db.sh >> /var/log/backup.log 2>&1

Step 4: Encrypted Backups to S3-Compatible Storage with restic

restic offers deduplication, encryption and snapshots, and works with AWS S3, Cloudflare R2, MinIO and any other S3-compatible service. Include the database dump directory from Step 3 to get an off-site copy of everything. forget --prune applies a daily/weekly/monthly retention policy and frees space.

# Install restic
apt install -y restic                                   # Debian / Ubuntu
dnf install -y epel-release && dnf install -y restic    # Rocky / AlmaLinux

# Repository settings for an S3-compatible bucket
cat > /root/.restic.env <<'EOF'
export AWS_ACCESS_KEY_ID=YOUR_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY
export RESTIC_REPOSITORY=s3:https://s3.example.com/my-backup-bucket/web01
export RESTIC_PASSWORD_FILE=/root/.restic.pass
EOF
openssl rand -base64 32 > /root/.restic.pass
chmod 600 /root/.restic.env /root/.restic.pass

. /root/.restic.env
restic init
restic backup /etc /var/www /backup/mysql
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic snapshots

# Schedule: crontab -e
40 3 * * * { . /root/.restic.env && restic backup -q /etc /var/www /backup/mysql && restic forget -q --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune; } >> /var/log/restic.log 2>&1
If you lose the restic repository password, the data cannot be recovered. Store /root/.restic.pass and the S3 keys somewhere safe, such as a password manager, not only on the server being backed up.

Step 5: Test Your Backup Restores Regularly

A backup that has never been restored is only a hope. Run a restore drill at least monthly: extract files into a temporary folder, import a dump into a test database and compare row counts, and run restic check. Ideally rebuild onto a fresh server once and note how long it takes.

# Files: list an archive and extract one directory to a temp folder
tar -tzf /backup/files/files-2026-10-01.tar.gz | head
mkdir -p /tmp/restore
tar -xzf /backup/files/files-2026-10-01.tar.gz -C /tmp/restore etc/nginx

# Database: import into a throw-away database and compare
mysql -e "CREATE DATABASE restore_test"
gunzip -c /backup/mysql/shop-2026-10-01_0220.sql.gz | mysql restore_test
mysql -e "SELECT COUNT(*) FROM restore_test.orders"
mysql -e "DROP DATABASE restore_test"

# restic: verify the repository and restore the latest snapshot
. /root/.restic.env
restic check
restic restore latest --target /tmp/restic-restore --include /etc/nginx

FAQ

Backups are filling up the disk. What now?

Lower KEEP_DAYS, exclude caches and logs (--exclude with tar) and move backups to another server or object storage. See our disk-space-full tutorial for cleanup tips.

The cron job never runs?

Check /var/log/backup.log and journalctl -u cron (crond on Rocky). Cron has a minimal PATH, so use full paths in scripts and make sure the script is executable.

Will dumping a large database slow down my site?

With InnoDB and --single-transaction tables are not locked, but the dump still uses disk I/O, so schedule it off-peak. For very large databases consider physical hot backups with mariadb-backup or Percona XtraBackup.

Still stuck after following these steps? Open a support ticket and the IMIDC 24/7 technical team will help. Include the server IP, OS version, the commands you ran and the full error output so we can pinpoint the issue faster.

Was this answer helpful?

Related Tutorials