We manage the VPS backups, and in the customer area you can restore the whole disk from them. For individual files, databases and a copy outside our infrastructure, you set up your own backups. We'll show both.
What you'll need
- A StormHost VPS with Ubuntu 24.04 or Debian 13
- An SSH connection as root (guide)
- For backups off the VPS, a second computer or server with SSH access
- About 25 minutes
Automatic StormHost backups#
We create VPS backups automatically. You'll find them in the customer area: My services › your VPS › Backups tab.
- For each backup, the list shows Created (date and time), Size and the Storage it's kept on.
- You can't create a backup manually from the customer area. Before a risky change (a system upgrade, a big configuration change), make your own backup as described in the rest of this guide.
- If you see “One of the backup storages isn't responding right now — the list may be incomplete”, some backups are temporarily unavailable. Try again later, or contact us.
- A member of a shared service only sees backups with the Backups and restore permission.
Restoring the whole VPS#
Restoring overwrites the whole disk
“Disk data will be irreversibly overwritten by the backup. The VPS shuts down during the restore.” Everything created on the VPS after the backup was made (new orders on your website, saved games, databases, logs) is lost. If the VPS holds anything like that, download it first (SFTP, mariadb-dump).
- In the Backups tab, find the backup by its date in the Created column.
- Click Restore.
- Read the warning and confirm it by moving the slider (“Slide to restore”).
- Wait for it to finish. During the restore the VPS is shut down and locked, and the console won't connect.
- After the restore, start the VPS (if it doesn't start by itself) and check your services.
Full restore, or individual files?#
| Situation | What to use |
|---|---|
| Broken system, compromised VPS, failed update | Restore the whole VPS in the Backups tab. |
| A deleted or overwritten file, a bad configuration edit | Your own file backup (below). Restoring the whole disk would roll back everything else too. |
| Damaged or deleted data in a database | Your own database backup (a mariadb-dump export). |
| Moving to another VPS or another provider | Your own backups, ideally stored off the VPS. |
The 3-2-1 rule#
A proven rule for backups: 3 copies of your data, in 2 different places or storage types, with 1 of them outside the main infrastructure.
For a VPS that means:
- The data on the VPS (the original copy).
- Our automatic backups (the second copy, outside your VPS).
- Your own backup elsewhere: a home NAS, another server, cloud storage (the third copy, outside our infrastructure).
The third copy protects you even when a restore in the customer area won't help: you need an older state, a single file, or you want to move elsewhere.
What to back up#
| Path / data | What it contains | Note |
|---|---|---|
/etc | System and service configuration (Nginx, SSH, cron, firewall). | Small; always back it up. |
/home, /root | Users' home directories, scripts, SSH keys. | /root/.ssh contains private keys, so encrypt the backup. |
/var/www | Websites. | |
/opt, /srv | Apps and data you install there (for example Docker Compose projects). | |
| Databases | MariaDB/MySQL data. | Always as an export (mariadb-dump), never by copying the /var/lib/mysql directory while it's running; that copy would be inconsistent. |
| Docker volumes | Container data (/var/lib/docker/volumes). | Export databases running in containers; see Docker and Docker Compose. |
| Game server data | Worlds, configuration, plugins. | The directory depends on your installation. Ideally save the server before backing up the world (save-all in Minecraft). |
| Crontab | Scheduled tasks. | crontab -l > /root/crontab-zaloha.txt |
Backup script#
Every day, the script exports all databases, archives the important directories and deletes backups older than 7 days.
Create the script file:
bash nano /usr/local/sbin/zaloha.shPaste the content:
/usr/ local/ bashsbin/ zaloha.sh #!/bin/bash set -euo pipefail umask 077 CIL=/var/backups/vps DNES=$(date +%F) UCHOVAT_DNI=7 mkdir -p "$CIL" # 1) databases (only if MariaDB/MySQL is installed) if command -v mariadb-dump >/dev/null 2>&1; then mariadb-dump --single-transaction --routines --events --all-databases | gzip > "$CIL/db-$DNES.sql.gz" fi # 2) files tar -czf "$CIL/soubory-$DNES.tar.gz" --ignore-failed-read /etc /home /root /var/www /opt /srv || [ $? -eq 1 ] # 3) delete old backups find "$CIL" -type f -name '*.gz' -mtime +"$UCHOVAT_DNI" -delete echo "$(date '+%F %T') backup done"Save (Ctrl+O, Enter), close (Ctrl+X) and make it executable:
bash chmod 700 /usr/local/sbin/zaloha.shchmod 700– only root can read and run the script.
What the script does:
| Line | Meaning |
|---|---|
#!/bin/bash | The script runs with the bash interpreter. |
set -euo pipefail | The script stops at the first error (-e), when an unset variable is used (-u) and when any command in a | pipeline fails (pipefail). That way you don't silently end up with a broken backup. |
umask 077 | New files are readable by root only. Backups contain passwords and keys. |
CIL=/var/backups/vps | The backup directory on the VPS (CIL = “target”). |
DNES=$(date +%F) | Today's date in the form 2026-10-01 (DNES = “today”), used in file names (soubory = “files”). |
UCHOVAT_DNI=7 | How many days to keep backups (UCHOVAT_DNI = “days to keep”). More days = more disk space. |
command -v mariadb-dump | Checks whether the database is installed; if not, the export is skipped. |
mariadb-dump … | gzip > … | Exports all databases without locking tables, compressed on the fly. Root logs in to the database via unix socket without a password. |
tar -czf … /etc /home … | Packs and compresses the listed directories into one archive. |
--ignore-failed-read | Doesn't fail when a directory doesn't exist or can't be read. |
|| [ $? -eq 1 ] | tar returns code 1 when a file changed while it was being read (for example a log). That's fine; a real error (code 2) stops the script. |
find … -mtime +7 -delete | Deletes archives older than 7 days. |
Try the script by hand:
bash /usr/local/sbin/zaloha.sh ls -lh /var/backups/vpsYou'll see
backup doneand the filesdb-….sql.gzandsoubory-….tar.gz. The messagetar: Removing leading '/' from member namesis normal: paths are stored in the archive without the leading slash.
Disk space
The backup sits on the same disk as the data. It takes up space, and if the disk fails or the VPS is compromised, it disappears along with the data. Keep an eye on free space (df -h) and copy backups off the VPS (see Copying off the VPS).
Running it automatically with cron#
Make sure cron is installed (if it already is, nothing happens):
bash apt -y install cronOpen root's crontab:
bash crontab -eOn the first run, choose the
nanoeditor.Add this line at the end:
text 30 3 * * * /usr/local/sbin/zaloha.sh >> /var/log/zaloha.log 2>&1Save and close the editor. You'll see
crontab: installing new crontab.
A crontab line has five time fields and a command:
| Field | Allowed values | In the example | Meaning |
|---|---|---|---|
| minute | 0–59 | 30 | at minute 30 |
| hour | 0–23 | 3 | at 3 a.m. |
| day of month | 1–31 | * | every day |
| month | 1–12 | * | every month |
| day of week | 0–7 (0 and 7 = Sunday) | * | every day of the week |
| command | /usr/ | what to run (always with the full path) | |
| redirection | >> /var/log/zaloha.log 2>&1 | append output and errors to the log |
More time examples:
| Entry | When it runs |
|---|---|
0 */6 * * * | every 6 hours (0:00, 6:00, 12:00, 18:00) |
15 2 * * 0 | every Sunday at 2:15 |
0 4 1 * * | on the first day of the month at 4:00 |
Times follow the VPS's time zone (timedatectl). Schedule backups for when the server has the least traffic.
Copying off the VPS#
Setting up SSH access to the target server#
Send the backups to another server (a home NAS with SSH, another VPS). In the examples, the target server has the IP address 198.51.100.25; use your own server's address instead.
On the target server (Ubuntu or Debian, logged in as root), create a user and a directory it can write to:
adduser zaloha
mkdir -p /srv/zalohy
chown zaloha:zaloha /srv/zalohy
chmod 700 /srv/zalohyadduser zaloha– creates the userzaloha. It asks for a password (you'll need it in step 2 below) and for optional details, which you can skip with Enter.mkdir -p /srv/zalohy– the backup directory.chown zaloha:zaloha– the directory belongs to thezalohauser so it can write there. Without this, both rsync and restic fail withPermission denied.chmod 700– nobody else can access the directory.
Password login must be allowed on the target server at least until you upload the key (step 2). On a NAS, follow its own instructions (user, shared folder, SSH enabled).
Then, on the VPS being backed up:
Create a passwordless SSH key used only for backups:
bash ssh-keygen -t ed25519 -f /root/.ssh/zaloha -N "" -C "zaloha-vps"-t ed25519– a modern key type.-f /root/.ssh/zaloha– where to save the key (createszalohaandzaloha.pub).-N ""– no key passphrase, so cron can use it without you.-C "zaloha-vps"– a key label.
Upload the public key to the target server (you'll enter the password of the
zalohauser on the target server):bash ssh-copy-id -i /root/.ssh/zaloha.pub [email protected]On the first connection SSH asks
Are you sure you want to continue connecting (yes/no/[fingerprint])?. Typeyes: the target server's fingerprint is saved to/root/.ssh/known_hosts, and cron won't be asked again.Create a connection shortcut. Open
nano /root/.ssh/configand add:/root/ .ssh/ textconfig Host zalohy HostName 198.51.100.25 User zaloha IdentityFile /root/.ssh/zalohaHost zalohy– the shortcut you'll use instead of the address.HostName– the target server's IP address or domain.User– the user on the target server.IdentityFile– the key used to log in.
Test the connection:
ssh zalohymust log in without a password. Log out withexit.
Working with keys in detail: SSH key.
rsync#
rsync only copies changed files, so it's fast even with large backups. It must be installed on both sides (apt -y install rsync).
rsync -az /var/backups/vps/ zalohy:/srv/zalohy/vps1/-a– archive mode: copies recursively and preserves permissions, times and links.-z– compresses data during the transfer./var/backups/vps/– what to copy. The trailing slash means “the directory's contents”, not the directory itself.zalohy:/srv/zalohy/vps1/– the target: the shortcut from~/.ssh/configand the directory on the target server.
Add the transfer as the last line of zaloha.sh, or to the crontab an hour after the backup:
30 4 * * * rsync -az /var/backups/vps/ zalohy:/srv/zalohy/vps1/ >> /var/log/zaloha.log 2>&1Why without --delete
The --delete flag would delete everything on the target that's missing on the VPS. If an attacker or a mistake deletes the backups on the VPS, the next run would delete them on the target too. Delete old backups on the target server there instead, for example with your own cron job using find … -mtime +30 -delete.
Keep in mind that an attacker who takes over the VPS can also use the zaloha key directly and delete the backups on the target. Only a copy that can't be reached from the VPS protects against that: for example a target server that pulls the backups itself (rsync run on the target), or storage with version history.
restic (advanced)#
restic stores backups encrypted and with deduplication: repeated data is stored only once, so you can keep dozens of daily versions with little space. It can back up to an SFTP server as well as to cloud storage.
Install restic:
apt -y install restic.Create a file with the repository password and restrict its permissions:
bash nano /root/.restic-heslo chmod 600 /root/.restic-hesloPut a long random password in the file (password generator).
Without the password you can't restore
restic encrypts the data. If you lose the password, nobody can decrypt the backups, not even you. Keep it off the VPS too (in a password manager).
Create a repository on the target server:
bash restic -r sftp:zalohy:/srv/zalohy/restic-vps1 --password-file /root/.restic-heslo init-r sftp:– a repository over SFTP;zalohy:/ srv/ zalohy/ restic-vps1 zalohyis the shortcut from~/.ssh/config.--password-file– the password file, so restic doesn't ask (required for cron).init– creates a new empty repository. You'll seecreated restic repository ….
Back up (you can put this command in the script instead of the
tararchive; keep the database export):bash restic -r sftp:zalohy:/srv/zalohy/restic-vps1 --password-file /root/.restic-heslo backup /etc /home /root /var/www /opt /srv /var/backups/vpsDelete old versions according to a policy:
bash restic -r sftp:zalohy:/srv/zalohy/restic-vps1 --password-file /root/.restic-heslo forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --pruneforget– removes versions (snapshots) the policy doesn't keep.--keep-daily 7– keeps the latest version from each of the last 7 days that have a backup.--keep-weekly 4,--keep-monthly 6– also one version per week for 4 weeks and one per month for 6 months.--prune– immediately deletes data no version refers to anymore (frees up space).
Useful commands:
Command (with -r … and --password-file …) | What it does |
|---|---|
snapshots | Lists stored versions with their dates. |
restore latest --target /tmp/obnova | Restores the latest version into /tmp/obnova. |
restore latest --target /tmp/obnova --include /etc/nginx | Restores only the selected directory. |
check | Checks the repository's integrity. |
An alternative to restic is BorgBackup (documentation (opens in a new window)), which works on a similar principle.
Testing a restore#
A backup you've never tried to restore is just hope. Test it every now and then:
List the archive's contents:
tar -tzf /var/.backups/ vps/ soubory-2026-10-01. tar. gz | head Extract a single file into a temporary directory:
bash mkdir -p /tmp/obnova tar -xzf /var/backups/vps/soubory-2026-10-01.tar.gz -C /tmp/obnova etc/nginx/nginx.conf-x– extract,-z– gzip format,-f– from a file.-C /tmp/obnova– where to extract (so the live file isn't overwritten).etc/nginx/nginx.conf– what to extract (path without the leading slash).
Check the database export:
zcat /var/must show the beginning of an SQL export.backups/ vps/ db-2026-10-01. sql. gz | head -n 20 Do a full test (restoring the database, starting the app) on a separate test VPS, not on your production one.
Restoring a database overwrites the current data
The command gunzip < db-….sql.gz | mariadb restores all databases from the backup and overwrites their current contents. Only run it on a production VPS when you really want to bring back the state from the backup. Restoring a single database is covered in MariaDB on a VPS.
Verification#
- The morning after the first night:
tail /var/log/zaloha.logcontains the linebackup done. ls -lh /var/backups/vpsshows new files with today's date and a sensible size (not 0 bytes).- The target server has the same files (rsync), or
restic snapshotsshows today's version.
Troubleshooting#
Cron didn't run the script#
Check that cron is running and what it wrote to the log:
systemctl status cron
journalctl -u cron -n 50Common causes: the crontab is missing the full path to the script, the script isn't executable (chmod 700), or you edited the crontab of a user other than root (crontab -l prints it).
mariadb-dump: Got error: 1045: "Access denied for user …"#
The script isn't running as root, or the database root uses a password instead of the unix socket. Run the script from root's crontab. If root has a database password, store it in /root/.my.cnf, where mariadb-dump reads it automatically:
[client]
user=root
password=<DB_ROOT_PASSWORD>[client]– the option group for all MariaDB client programs (mariadb,mariadb-dump).user,password– the credentials. Replace<DB_ROOT_PASSWORD>with the password of the database userroot(without the angle brackets).
Secure the file with chmod 600 /root/.my.cnf so nobody else can read the password.
The backup is 0 bytes or suspiciously small#
The script failed partway through. Run it by hand and read the error. Thanks to set -euo pipefail, an export error doesn't go unnoticed.
Permission denied (publickey) with rsync or restic#
The target server didn't accept the key. Test ssh zalohy. If it asks for a password or fails, upload the key again (ssh-copy-id) and check that IdentityFile in /root/.ssh/config points to the right file.
restic: Fatal: wrong password or no key found#
The password in /root/.restic-heslo doesn't match the repository. Check that the file doesn't contain an extra space or a different password.
restic: Fatal: unable to open config file … Is there a repository at the following location?#
There's no repository at the given path (a typo in the path, or you didn't run init).
The disk is filling up#
Backups aren't being deleted, or they're too large. Check that the find … -delete step runs, lower UCHOVAT_DNI and leave out directories with large data that you back up another way.
Summary
- We manage the VPS backups; in the Backups tab you restore the whole disk, and any newer data is lost.
- Individual files and databases can only be restored from your own backups.
- Back up databases with a
mariadb-dumpexport, files with atararchive or with restic. - Run the script with cron and send the backups off the VPS (rsync without
--delete, restic with encryption). - Test restoring regularly.