IPv4 & IPv6 Leasing - Any RIR, Any LocationOrder Now
Hostperl

Dedicated Server ZFS Backup Rotation on openSUSE Leap

By Raman Kumar

Share:

Updated on Oct 3, 2026

Dedicated Server ZFS Backup Rotation on openSUSE Leap

What you are building

This tutorial shows you how to set up a dedicated server ZFS backup rotation on openSUSE Leap with local ZFS snapshots and offsite file-based backups. The aim is straightforward: protect a production bare metal server, keep clean rollback points, and prove that restore steps work before you need them in an emergency. That matters for real Hostperl customers running databases, application stacks, or control panels on a dedicated server who cannot afford guesswork during an incident.

You will start with a fresh SSH login, identify the operating system, create a non-root administrator, install the required tools, build a ZFS dataset layout, schedule automatic snapshots, push encrypted backup archives offsite, and verify retention plus restore. If your server is not on ZFS yet, this tutorial still helps you plan the rotation and understand the recovery workflow before you put production data on it. For dedicated-server buyers who want tighter control over storage and recovery, Hostperl’s dedicated server hosting and dedicated servers pages are the right place to start.

Scenario and design choices

We are protecting a single production server on openSUSE Leap that stores application files and a database dump directory. ZFS gives you point-in-time snapshots and quick rollback, while an offsite backup copy protects you if the machine, disk set, or datacenter has trouble. That split matters. Snapshots are fast recovery points, but they are not a backup by themselves.

The design here uses:

  • Local ZFS snapshots for quick restore and rollback.
  • Offsite compressed archives for disaster recovery.
  • systemd timer jobs so the rotation runs without cron drift.
  • openSUSE Leap package and firewall tooling so the procedure matches the platform.

If your workload is larger, a self-managed or enterprise dedicated server from Hostperl may fit better than a small VPS, especially when you need direct storage control, repeatable restore tests, or hardware-level isolation.

1) Connect to the server and confirm the OS

On your local computer, open your first SSH session:

ssh root@203.0.113.10

The address 203.0.113.10 is only an example. Replace it with the real public IP assigned to your server. Keep this root session open until the new administrator account is verified.

On the VPS as root, confirm the platform:

cat /etc/os-release

You should see openSUSE Leap details in the output. This tutorial targets openSUSE Leap because the package manager, firewall service, and systemd timer behavior are specific to it. For a quick kernel and storage check, run:

uname -r
lsblk -f
zpool list

If zpool is not installed or no pools exist yet, you can still complete the non-ZFS parts of the procedure. The snapshot sections need an active ZFS pool.

2) Create a non-root administrator for daily work

Do not run routine backup work as root. Create a sudo-capable admin named deploy, then switch to that account after key-based login works.

On the VPS as root, create the user and grant sudo access:

useradd -m -s /bin/bash deploy
passwd deploy
usermod -aG wheel deploy

passwd deploy sets a password for the first login test. wheel is the standard administrative group on openSUSE-compatible systems. Leave the root session open.

Now create the SSH directory and lock permissions:

install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
chown deploy:deploy /home/deploy/.ssh/authorized_keys
chmod 600 /home/deploy/.ssh/authorized_keys

If your provider stores the public key somewhere else, copy the real key instead of /root/.ssh/authorized_keys. The key file must be readable only by the new user.

On your local computer, open a second terminal and test the new login:

ssh deploy@203.0.113.10

Then test sudo:

sudo -v
whoami

If this works, whoami still returns deploy, and sudo is ready when you need root access. Do not disable root login until this step succeeds.

3) Update packages and install ZFS tools

On the VPS as the non-root sudo user, refresh the package cache and install the tools used in this tutorial:

sudo zypper refresh
sudo zypper update -y
sudo zypper install -y zfs zfs-kmp-default rsync gzip openssl util-linux

On openSUSE Leap, ZFS support comes from the ZFS on Linux packages available for the distribution. After installation, verify the tools:

sudo zpool version
sudo zfs version
rsync --version | head -n 1

If the ZFS packages are missing from your repository setup, stop here and fix the repo configuration before you touch production storage. That is safer than building a backup plan on a missing filesystem layer.

4) Create the backup dataset layout

This example assumes you already have a ZFS pool named tank. If your pool name differs, substitute it everywhere below.

On the VPS as the non-root sudo user, inspect the pool:

sudo zpool status tank

Next create a backup parent dataset and two child datasets, one for application files and one for database dumps:

sudo zfs create -o mountpoint=/srv/backups tank/backups
sudo zfs create -o mountpoint=/srv/backups/app tank/backups/app
sudo zfs create -o mountpoint=/srv/backups/db tank/backups/db

Check the mountpoints:

sudo zfs list -o name,mountpoint,used,avail

This layout keeps backups separate from live application paths, which makes retention and restore testing much easier. If you already have a production dataset, you can still use it. Just point the scripts at the correct source paths.

5) Create the backup script

We will create one script to capture the backup set, another to rotate snapshots, and a systemd unit to automate the job. Start with the main archive script.

On the VPS as the non-root sudo user, create the script directory:

sudo install -d -m 750 -o root -g wheel /usr/local/sbin

Then create the backup script:

sudo vi /usr/local/sbin/dedicated-server-backup.sh

Paste this content, save, and exit:

#!/usr/bin/env bash
set -euo pipefail

BACKUP_ROOT="/srv/backups"
STAMP="$(date +%F-%H%M%S)"
HOST="$(hostname -s)"
TMPDIR="$(mktemp -d)"
ARCHIVE="${BACKUP_ROOT}/db/${HOST}-db-${STAMP}.tar.gz"
APP_ARCHIVE="${BACKUP_ROOT}/app/${HOST}-app-${STAMP}.tar.gz"

cleanup() {
  rm -rf "${TMPDIR}"
}
trap cleanup EXIT

mkdir -p "${TMPDIR}/db" "${TMPDIR}/app"

if [ -d /var/lib/mysql ]; then
  rsync -aHAX --delete /var/lib/mysql/ "${TMPDIR}/db/mysql/"
fi

if [ -d /var/lib/pgsql ]; then
  rsync -aHAX --delete /var/lib/pgsql/ "${TMPDIR}/db/pgsql/"
fi

if [ -d /opt/myapp ]; then
  rsync -aHAX --delete /opt/myapp/ "${TMPDIR}/app/myapp/"
fi

tar -C "${TMPDIR}" -czf "${ARCHIVE}" db
chmod 600 "${ARCHIVE}"

tar -C "${TMPDIR}" -czf "${APP_ARCHIVE}" app
chmod 600 "${APP_ARCHIVE}"

find "${BACKUP_ROOT}" -type f -name '*.tar.gz' -mtime +14 -delete

This script copies common application and database paths into compressed archives. Replace /opt/myapp with your real application directory if needed. The retention rule removes archives older than 14 days.

Make it executable:

sudo chmod 750 /usr/local/sbin/dedicated-server-backup.sh
sudo chown root:wheel /usr/local/sbin/dedicated-server-backup.sh

6) Add a snapshot rotation script

Snapshots give you a fast rollback point before an update or deployment. This script creates a snapshot of the dataset tree and removes snapshots older than seven days.

On the VPS as the non-root sudo user, create the script:

sudo vi /usr/local/sbin/zfs-snapshot-rotate.sh

Paste the content below, save, and exit:

#!/usr/bin/env bash
set -euo pipefail

POOL="tank"
DATASET="${POOL}/backups"
STAMP="$(date +%F-%H%M%S)"
SNAP="${DATASET}@auto-${STAMP}"

sudo zfs snapshot -r "${SNAP}"

sudo zfs list -t snapshot -o name,creation -s creation | awk '/@auto-/ {print $1}' | head -n -7 | while read -r oldsnap; do
  [ -n "${oldsnap}" ] && sudo zfs destroy "${oldsnap}"
done

Make it executable:

sudo chmod 750 /usr/local/sbin/zfs-snapshot-rotate.sh
sudo chown root:wheel /usr/local/sbin/zfs-snapshot-rotate.sh

If your actual pool name is not tank, change it in the script before running it.

7) Create a systemd service and timer

systemd is a better fit than cron here because you can inspect the unit state, logs, and missed runs from one place. Create a service for the archive job first.

On the VPS as the non-root sudo user, create the unit file:

sudo vi /etc/systemd/system/dedicated-server-backup.service

Use this content:

[Unit]
Description=Dedicated server backup archive job
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/dedicated-server-backup.sh

Save and exit, then create the timer:

sudo vi /etc/systemd/system/dedicated-server-backup.timer

Use this content:

[Unit]
Description=Run dedicated server backup archive nightly

[Timer]
OnCalendar=03:15
Persistent=true
Unit=dedicated-server-backup.service

[Install]
WantedBy=timers.target

Reload systemd and enable the timer:

sudo systemctl daemon-reload
sudo systemctl enable --now dedicated-server-backup.timer

Check the timer status:

systemctl list-timers --all | grep dedicated-server-backup

Now add the snapshot rotation unit and timer with the same pattern if you want automatic local rollback points. Create the service:

sudo vi /etc/systemd/system/zfs-snapshot-rotate.service

Content:

[Unit]
Description=Rotate ZFS snapshots for backup datasets

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/zfs-snapshot-rotate.sh

Create the timer:

sudo vi /etc/systemd/system/zfs-snapshot-rotate.timer

Content:

[Unit]
Description=Run ZFS snapshot rotation nightly

[Timer]
OnCalendar=02:45
Persistent=true
Unit=zfs-snapshot-rotate.service

[Install]
WantedBy=timers.target

Reload and enable:

sudo systemctl daemon-reload
sudo systemctl enable --now zfs-snapshot-rotate.timer

8) Test the scripts before trusting them

Always run the jobs manually first. That catches permissions issues before the first scheduled window.

On the VPS as the non-root sudo user, run the archive job:

sudo systemctl start dedicated-server-backup.service
sudo systemctl status dedicated-server-backup.service --no-pager
journalctl -u dedicated-server-backup.service -n 50 --no-pager

Successful output should show exit code 0 and new archives in /srv/backups/db and /srv/backups/app:

sudo ls -lh /srv/backups/db /srv/backups/app

Then run the snapshot rotation job:

sudo systemctl start zfs-snapshot-rotate.service
sudo systemctl status zfs-snapshot-rotate.service --no-pager
sudo zfs list -t snapshot | grep auto- | tail

If snapshots appear, the local rollback layer is working.

9) Open the firewall only if you need remote backup transport

This tutorial does not expose a public backup port by default. If you later decide to sync archives to another host over SSH, open only the port you actually use and test it before closing any older method.

On the VPS as root, check whether the firewall service is running:

sudo systemctl status firewalld --no-pager

If you use firewalld on openSUSE Leap, add SSH access only if needed for normal administration:

sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

This is already allowed on many provider images, but verifying it avoids surprises after a reboot.

10) Send an offsite copy

Snapshots protect local recovery. Offsite backups protect the business. For a simple second copy, use another server with SSH access and a destination directory reserved for backups.

On the VPS as the non-root sudo user, test connectivity to the remote host first:

ssh backupuser@backup.example.net 'pwd'

Replace backupuser@backup.example.net with your real offsite target. If that works, sync one backup set with rsync:

rsync -aHAX --numeric-ids --info=progress2 /srv/backups/ backupuser@backup.example.net:/data/backups/203.0.113.10/

This creates a full mirror of the local backup tree. If you prefer encryption at rest, store the archives inside an encrypted destination or encrypt them before transfer. For larger environments, Hostperl customers often pair a dedicated server with extra storage capacity or another regional system to keep recovery paths separated.

11) Verify restore from a snapshot and from an archive

A backup is not proven until you restore something from it. First verify the snapshot layer by cloning one snapshot into a test dataset.

On the VPS as the non-root sudo user, list snapshots:

sudo zfs list -t snapshot -o name,creation | tail -n 5

Pick one recent snapshot and clone it to a temporary test dataset:

sudo zfs clone tank/backups@auto-2026-01-01-020000 tank/backups-restore-test

Replace the snapshot name with one that exists on your server. Then mount and inspect it:

sudo zfs get mountpoint tank/backups-restore-test
sudo ls -lah /srv/backups

When you are done, destroy the clone so it does not consume space:

sudo zfs destroy -r tank/backups-restore-test

Now test an archive restore. Create a scratch directory, extract one file, and confirm its permissions and contents:

mkdir -p /tmp/restore-test
sudo tar -xzf /srv/backups/app/$(ls -1 /srv/backups/app | tail -n 1) -C /tmp/restore-test
find /tmp/restore-test -maxdepth 2 -type f | head

If the extracted tree matches the application layout you expect, your archive layer is usable for a real incident.

12) Add a safe rollback path for application changes

Before any package upgrade or deployment, take a manual snapshot. That gives you a fast rollback if the application update breaks startup or data access.

On the VPS as the non-root sudo user, create a pre-change snapshot:

sudo zfs snapshot -r tank/backups@pre-change-$(date +%F-%H%M)

After your maintenance window, verify service health, then destroy the snapshot only when you are comfortable with the outcome:

sudo zfs list -t snapshot | grep pre-change
sudo zfs destroy -r tank/backups@pre-change-2026-01-01-0300

Replace the snapshot name with the one created on your server. Never destroy the only rollback point before the change is validated.

13) Check reboot persistence

Timers and storage mounts must survive reboot. Check the configured units:

systemctl is-enabled dedicated-server-backup.timer
systemctl is-enabled zfs-snapshot-rotate.timer
sudo zfs list | grep /srv/backups

Then schedule a maintenance reboot if your change window allows it:

sudo reboot

Reconnect after the reboot from your local computer:

ssh deploy@203.0.113.10

Confirm the timers and ZFS mounts came back:

systemctl list-timers --all | grep -E 'dedicated-server-backup|zfs-snapshot-rotate'
sudo zpool status tank
sudo zfs list -o name,mountpoint | grep backups

14) Troubleshooting the most likely failures

Problem: the backup service fails with permission denied.
Run:

journalctl -u dedicated-server-backup.service -n 100 --no-pager
ls -ld /srv/backups /srv/backups/app /srv/backups/db

Look for ownership or mode problems. Fix them with:

sudo chown -R root:wheel /srv/backups
sudo chmod -R u+rwX,g-rwx,o-rwx /srv/backups

Problem: the ZFS snapshot script cannot find the pool.
Run:

sudo zpool list
sudo zfs list

If the pool name is different from tank, edit /usr/local/sbin/zfs-snapshot-rotate.sh and replace it. Then reload the timer with:

sudo systemctl daemon-reload
sudo systemctl start zfs-snapshot-rotate.service

Problem: rsync to the offsite host fails.
Run:

ssh -vv backupuser@backup.example.net
rsync -e ssh -av /srv/backups/ backupuser@backup.example.net:/data/backups/203.0.113.10/

The clue is usually an SSH key or host key mismatch. Fix the remote account, or refresh the known host entry after confirming the destination is correct.

Problem: the timer did not run after a reboot.
Run:

systemctl status dedicated-server-backup.timer --no-pager
journalctl -u systemd-timer --since today --no-pager

If the timer is disabled, re-enable it with:

sudo systemctl enable --now dedicated-server-backup.timer
sudo systemctl enable --now zfs-snapshot-rotate.timer

15) What good looks like in production

You should end with three working layers: local snapshots, nightly archives, and an offsite copy. That gives you a fast rollback for routine mistakes, a local restore path for file-level recovery, and a separate disaster-recovery copy if the server is lost.

This is the kind of operational discipline that matters on a dedicated server. It protects migrations, patch windows, and support handoffs, and it gives you evidence when a customer asks whether a restore was actually tested. If you are planning the next machine in the cluster, Hostperl’s self-managed dedicated server and enterprise dedicated hosting options fit teams that want storage control and recovery procedures they can document.

If you need a dedicated server ZFS backup rotation that you can support in production, Hostperl can help you choose the right server profile and recovery path. For workloads that need direct storage control, look at dedicated server hosting; for larger environments with heavier recovery demands, review enterprise dedicated hosting.

Our support team works through migrations, restore checks, and launch readiness with the same practical focus you used in this tutorial.

FAQ

Is ZFS required for this backup rotation?
No. The offsite archive portion works on any Linux filesystem. ZFS only adds fast local snapshots and rollback.

Can I use cron instead of systemd timers?
Yes, but systemd gives you clearer logging and easier verification on openSUSE Leap.

Should snapshots replace offsite backups?
No. Snapshots protect against operator mistakes. Offsite backups protect against disk, host, and site failure.

What if my application is not stored in /opt/myapp?
Edit /usr/local/sbin/dedicated-server-backup.sh and point it at your real application path before the first scheduled run.

How often should I test restores?
Test at least once after setup, then on a fixed schedule such as monthly or after any major change.