Back up a server with rsync and cron (files and MySQL)

VPS & Servers

0 people found this article helpful

A small script dumps your databases and copies your files with rsync to a second server over SSH. A cron job runs it every night.

Good to know: a backup on the same server isn't a backup: if the disk fails or the server is hacked, it's gone too. You need a second machine you can reach over SSH, for example another VPS or a storage server. The commands are for Ubuntu 24.04 LTS; rsync and cron work the same on Ubuntu 26.04 LTS and most other Linux systems. Replace 203.0.113.20 with the IP address of the backup server.

1. Prepare the backup server

On the backup server, create a user that only receives backups, and a folder for this server's backups:

sudo adduser serverbackup
sudo mkdir -p /backups/web1
sudo chown serverbackup:serverbackup /backups/web1

Don't call the user backup: Ubuntu already has a system user with that name, which can't log in. Make sure rsync is installed on both servers (sudo apt install rsync; it usually is).

2. Give the web server a key for the backup server

The script runs as root without anyone typing a password, so it needs its own SSH key without a passphrase. On the server you want to back up:

sudo ssh-keygen -t ed25519 -f /root/.ssh/backup_key -N ""
sudo ssh-copy-id -i /root/.ssh/backup_key.pub [email protected]

ssh-copy-id asks for the serverbackup user's password once. Then test the login, and answer yes to the fingerprint question so cron doesn't get stuck on it later:

sudo ssh -i /root/.ssh/backup_key [email protected] 'echo ok'

It should print ok without asking for a password.

3. Write the backup script

sudo nano /usr/local/bin/server-backup.sh

Paste this and adjust the first lines:

#!/bin/bash
set -euo pipefail
umask 077

REMOTE="[email protected]"
REMOTE_DIR="/backups/web1"
KEY="/root/.ssh/backup_key"
DUMP_DIR="/var/backups/mysql"
TODAY=$(date +%F)

# 1. Dump all databases into one file per day, keep 7 days
mkdir -p "$DUMP_DIR"
mysqldump --single-transaction --routines --events --all-databases | gzip > "$DUMP_DIR/all-$TODAY.sql.gz"
find "$DUMP_DIR" -name 'all-*.sql.gz' -mtime +7 -delete

# 2. Copy websites, settings and dumps to the backup server.
#    Files that were changed or deleted since the last run are kept in changed-DATE.
rsync -a --delete -e "ssh -i $KEY" \
  --backup --backup-dir="$REMOTE_DIR/changed-$TODAY" \
  /var/www /etc "$DUMP_DIR" \
  "$REMOTE:$REMOTE_DIR/current/"

echo "$(date) backup finished"

Save with Ctrl+O, Enter, close with Ctrl+X, and make the script executable for root only:

sudo chmod 700 /usr/local/bin/server-backup.sh

What the script does:

  • mysqldump --single-transaction saves the databases while the website keeps running. As root on Ubuntu it needs no password. With MariaDB, write mariadb-dump instead of mysqldump. No database on the server? Delete the three lines under "1.".
  • rsync -a copies folders with their permissions and dates, and only sends what changed since the last run, so later runs are quick.
  • --delete also removes files on the backup server that you deleted on the web server, so current is an exact copy. --backup-dir keeps those deleted and overwritten files in a folder per day, so a mistake doesn't wipe your backup.
  • umask 077 makes the dump files readable by root only. The backup contains passwords and customer data: protect the backup server as well as the web server.
  • The backup user isn't root, so the backup server doesn't keep the original file owners. That's harmless: when you restore as root, set the owners again if needed, for example sudo chown -R www-data:www-data for upload folders.
  • /var/www /etc are examples: add every folder you need, such as /home or the folder of your app.

4. Run it once by hand

Try the copy first without changing anything on the backup server (-n is a dry run, -v lists the files):

sudo rsync -anv --delete -e "ssh -i /root/.ssh/backup_key" /var/www /etc [email protected]:/backups/web1/current/

If the list looks right, run the real backup:

sudo /usr/local/bin/server-backup.sh

The first run copies everything and takes the longest. It ends with backup finished. On the backup server, check ls /backups/web1/current: you should see www, etc and mysql.

5. Run it every night with cron

Open root's cron table:

sudo crontab -e

Add this line at the end, for a backup at 2:30 every night (server time), with a log file:

30 2 * * * /usr/local/bin/server-backup.sh >> /var/log/server-backup.log 2>&1

The five fields are minute, hour, day of month, month and day of week. Check the entry with sudo crontab -l. The next morning, look at the end of the log:

sudo tail -n 20 /var/log/server-backup.log

The changed-DATE folders on the backup server grow over time. Clean up old ones there, for example older than 30 days, with a cron job on the backup server:

find /backups/web1 -maxdepth 1 -name 'changed-*' -mtime +30 -exec rm -rf {} +

Restore from the backup

Files, for example one website (the slashes at the end matter: they copy the folder's contents into the target folder):

sudo rsync -a -e "ssh -i /root/.ssh/backup_key" [email protected]:/backups/web1/current/www/example.com/ /var/www/example.com/

Databases: copy the dump you want back to the server, then import it. This overwrites the databases in the file with their state on that day:

gunzip < all-2026-11-01.sql.gz | sudo mysql

Test a restore now and then, for example on a test server. A backup you've never restored is only a hope.

Common problems

Permission denied (publickey) when cron runs

The script runs as root, so it uses root's key. Check that you created the key with sudo and that the test login in step 2 works with sudo.

Host key verification failed

Root has never connected to the backup server, or the backup server was reinstalled. Run the test login from step 2 again and confirm the fingerprint.

The cron job doesn't run

Check that the line is in sudo crontab -l and that the script is executable. Cron's own messages are in the system journal: journalctl -u cron --since today. Use full paths in scripts that cron runs.

mysqldump: Access denied

Run the script as root (with sudo or from root's crontab). If your database's root user has a password instead of the system login, put the login details into /root/.my.cnf with permissions 600.

Related articles

Still stuck? Open a support ticket and we'll help.


Was this answer helpful?

2FA 301 redirect 3D Secure 404 page 500 error 550 error A record accept quote access log account credit account details account password Account Preferences account security add funds addon domain address importer affiliate program AI assistant AlmaLinux 9 Android email Apache Apache virtual host API token Apple Mail Ask AI auth code authenticator app auto-renew automatic payment automatic updates autoresponder AutoSSL bandwidth billing address billing contact billing section block editor block IP bounce bounce message BoxTrapper build a WordPress website bulk email accounts business email buy domain buy hosting buy SSL certificate CalDAV calendar sync callback can't log in cancel hosting cancellation card declined CardDAV catch-all Certbot change domain WordPress change email change nameserver change package change password change payment checklist checkout choose a plan client accounts client area clone WordPress cloud server coming soon page commission company name confirmation link contact contact form Contact Information contacts control panel cPanel cPanel API cPanel contact email cPanel DNS cPanel domains cPanel email cPanel install cPanel language cPanel layout cPanel login cPanel password cPanel SSL cPanel update cPanel users create cPanel account create mailbox credit card cron cron job CRT key CA bundle CSF firewall CSV import currency custom error page Custom Nameservers custom offer dashboard data center database password database privileges database user Database Wizard date.timezone DDNS deceptive site ahead default address delete database delete old emails deny access deploy deposit device email settings Directory Privacy disk space disk usage DKIM DMARC DNS cache DNS explained DNS Records DNS Zone Management document root domain alias domain grace period Domain Hosting Domain Management domain name domain renewal domain transfer downgrade download invoice due date dynamic DNS ElySpace email account email account exists email backup email client email delivery email disk usage email filters email forwarder email history email hosting email login email migration email password email ports email preferences email quota email routing email signature email test email verification EPP code error error log error pages EV SSL expired domain expiry expiry date export database export emails fake email faster support feedback File Manager file permissions firewall first website flush DNS folder force HTTPS forgot password form email not received forward email free migration free SSL FTP account's password FTP password Galaxy email setup getting started Git GitHub Gmail Gmail app GoDaddy good ticket Google malware warning Google sign-in Google Workspace GST number Gzip compression hacked website hacked WordPress hardening help help center home server hosting hosting addons hosting glossary hosting location hosting plan Hosting Service hosting terms hosting tools hosting types hostname hostname SSL hostnamectl hotlink protection HOW TO ADD NAME SERVERS TO MY DOMAIN NAME How to create additional FTP accounts in cPanel How to create FTP accounts in cPanel How to setup name-servers for a domain htaccess htaccess redirect HTTP to HTTPS image theft IMAP IMAP settings IMAP SMTP settings import emails import SQL Imunify360 index.php virus INR install plugin install SSL certificate install WordPress instant answers invite user invoice invoice details invoice emails invoice PDF invoices IP address IP Blocker iPad iPhone email knowledgebase language large database leech protection LEMP Let's Encrypt LFD linked accounts LiteSpeed Cache live chat locked out log out login login email login problem lost email Mac Mac email mail rules mailbox mailbox check mailbox full mailbox size mailing list Mailman maintenance maintenance mode malware removal malware scan manage services mass payment max_execution_time members area memory_limit menu migrate WordPress migration request migration ticket mobile webmail money-back guarantee move email move website move WordPress site MultiPHP INI Editor MultiPHP Manager multiple accounts multiple websites MX record MX records my domains MySQL MySQL database MySQL user mysqldump nameserver Nameservers network drive new account new order newsletter Nginx Node.js notifications npm nvm open ticket Optimize Website or delete order order email out of office outage Outlook Outlook app Outlook IMAP Outlook mobile Outlook password prompt OX App Suite OX webmail page speed password protect pay all pay invoice payment payment details payment failed payment method payment methods PayPal permalinks permissions phishing phone support PHP errors PHP timezone PHP version PHP-FPM php.ini PHPMailer phpMyAdmin plugin updates point domain print invoice printer SMTP product addons products professional email profile public_html publish a post quick start quota quote quotes rating Raw Access raw access logs reading settings redemption period redirect referral link refund register register domain remove user renew domain renewal repair database reseller hosting reset admin password reset password resource usage restore WordPress revoke access revoke privileges root login Roundcube Roundcube export Roundcube identities Roundcube signature rsync backup Samsung Email scam scan to email scheduled task search Search Console self-hosted WordPress send and receive send as server IP address server location server security server status service details services session setup setup domain i setup domain name shared passwords shared vs VPS sign up single sign-on site address Sitejet Builder SMTP SMTP settings spam spam filter SpamAssassin SPF SSH SSH hardening SSH key SSL certificate static front page strong password subaccount subdomain suggestions support ticket support ticket tips support tickets suspension switch account tar test site Thunderbird ticket reply ticket status track delivery transfer website troubleshooting two-factor authentication UAPI Ubuntu Ubuntu 24.04 UFW unpaid invoice unsubscribe unzip upcp upgrade hosting UPI upload files upload_max_filesize URL forwarding user management User Manager username vacation reply VAT ID verify email Web Disk web hosting web hosting basics web server WebDAV webmail webmail From address webmail on phone webmail password website builder website email website migration website not loading Website Performance website security welcome email WhatsApp WhatsApp support whitelist WHM wildcard SSL WordPress 404 WordPress backup WordPress caching WordPress critical error WordPress email WordPress errors WordPress homepage WordPress hosting WordPress login WordPress malware WordPress Management WordPress menu WordPress offline WordPress pages WordPress password WordPress plugins WordPress post WordPress security WordPress SMTP WordPress speed WordPress staging WordPress themes WordPress transfer WordPress update WordPress URL WordPress.org vs WordPress.com WP Mail SMTP wp-admin wp-admin login wp-cron zip Zone Editor zone templates