ZFS scrub schedule for a small VPS pool
A scrub reads every allocated block and verifies it. On a single device VPS pool it finds damage it cannot repair, so here is how often to run one.
What a ZFS scrub actually does
A ZFS scrub reads every allocated block in the pool, recomputes its checksum, and compares the result against the checksum stored in the parent block pointer. Where the two disagree, ZFS repairs the block from whatever redundancy the pool holds. Nothing else in ZFS does this job. Ordinary reads verify only the blocks you happen to touch, so a file you have not opened in two years stays unverified until a scrub reads it.
A scrub is not fsck, the offline repair pass other filesystems need. There is no structural repair phase, because ZFS never leaves the on-disk format in a broken state: every write goes to a new location and the uberblock, the pool's root pointer, is updated last. A scrub also does not read the whole device. It reads allocated blocks only, which is why an almost empty pool scrubs in minutes and the same pool at 80% full takes far longer.
The scrub runs at the lowest I/O priority ZFS has. On Linux, zfs_vdev_scrub_max_active defaults to 2, so at most two scrub reads are in flight per vdev (virtual device, the group of disks ZFS treats as one unit). zfs_scrub_min_time_ms defaults to 750, the minimum time the sync thread spends on scrub work between transaction group flushes, the periodic commits ZFS batches writes into. On an idle machine the scrub uses the whole disk. Under load it steps aside. On a pool with one or two devices there is nowhere to step aside to, which is why scheduling matters more here than it does on a large chassis with sixty drives.
Why scrub a pool that cannot repair itself?
This is the sentence that decides everything else on a small pool. With no redundancy, a scrub detects corruption and cannot fix it. A single virtual disk in a VPS is a pool with no mirror and no parity. ZFS will read the bad block, fail the checksum, count it in the CKSUM column, name the file, and stop there, because there is no second copy to rebuild from.
Two partial exceptions are worth knowing. ZFS stores an extra copy of metadata by default (redundant_metadata=all), written to a different region of the device, so a scrub can repair a damaged directory entry or block pointer even on a one-device pool. And a dataset with copies=2 keeps two copies of its data blocks, at double the space cost. Neither survives the device going away. The copies property documentation warns about exactly this: do not build a striped pool, set copies=2, and believe you have redundancy.
So on a single-device pool the scrub gives you one thing: early and accurate notification. It turns silent corruption into a filename in zpool status -v while your backup still holds a good version of that file. That is an argument for backups, not an argument against scrubbing. If you have not settled the difference between a point-in-time image and a real off-box copy, start with why a VPS snapshot is not a backup, because a scrub result only helps if something else holds an intact copy.
A scrub that finds nothing is also a result. It tells you the data you are about to trust is intact, which is what you want to know before a restore or a migration.
How often should you scrub a small VPS pool?
Monthly is the right default, and it is what the packages already assume. Debian and Ubuntu ship a cron job that scrubs healthy pools on the second Sunday of each month. FreeBSD's periodic system works from a threshold in days, and daily_scrub_zfs_default_threshold defaults to 35, which the manual describes as five weeks.
Weekly scrubbing on a busy small pool usually costs more than it returns. With one or two devices the scrub competes for the same queue as your application, and there is no spare device to absorb it. On a VPS the I/O allowance is finite, so reads the scrub spends are reads your database does not get. Against that cost, weekly scrubbing gives you at most three weeks of earlier warning about a fault you cannot repair anyway. That trade only makes sense when the scrub is cheap.
Time it, then decide. Run one scrub by hand and watch how long it takes.
- Run
sudo zpool scrub tankon a quiet evening and record the total time fromzpool status. - If it finished well under an hour and the box is idle overnight, weekly is affordable.
- If it ran for many hours while the pool was serving traffic, stay monthly and let the packaged job own it.
- Re-time it whenever the pool grows noticeably, because scrub duration tracks allocated data, not disk capacity.
Whatever you choose, write it down next to your other recurring server work. A scrub belongs in the same list as package upgrades and log rotation: see a monthly Linux server maintenance checklist.
Start, pause and stop a scrub
sudo zpool scrub tank
sudo zpool status tankPausing and stopping are different operations, and picking the wrong one can cost you hours of repeated work.
sudo zpool scrub -p tank
sudo zpool scrub tank
sudo zpool scrub -s tank-p pauses. Pause state and progress are synced to disk periodically, so a paused scrub survives an export or a reboot: the pool comes back with the scrub still paused, waiting for you. Running zpool scrub again resumes from the last checkpoint written to disk. -s stops the scrub instead, and the next scrub you start begins from the beginning. Use -p when you need the disk back for an hour. Use -s when you want the scrub gone.
Two more flags are worth knowing. -w waits until the scrub finishes before returning, which is what you want inside a script so the next step does not start early. -e scrubs only the files with known data errors as reported by zpool status -v, which is the quick way to confirm that a file you restored from backup is now clean.
ZFS runs one scrub or resilver, the rebuild that follows replacing a device, at a time per pool, because both are I/O intensive. If a device is resilvering, your scrub waits its turn.
How to read zpool status while a scrub runs
Run sudo zpool status tank and read your own numbers rather than matching them against someone else's. During a scrub the scan: line carries a scanned figure, an issued figure, a total, a repaired figure, a percentage done, and an estimate of time remaining.
Scanned is the metadata phase: ZFS walking the block tree and collecting the addresses it needs to read. Issued is the data phase: the reads actually sent to the device, sorted into disk order. Sorted scrub is the reason there are two counters, and issued is the one that tracks real progress. Early on, scanned runs far ahead of issued and the time estimate means very little. Judge it after the first ten percent.
Repaired counts bytes rewritten from a good copy. On a pool with no redundancy this stays at zero no matter what the scrub finds, which is the earlier point restated as a number you can watch.
Then read the per-device columns. READ and WRITE count I/O errors the device itself reported. CKSUM counts blocks that failed checksum verification, and CKSUM is the column a scrub exists to fill. A non-zero CKSUM on a device that looks healthy is real: the data came back, and it came back wrong.
The last line is the verdict. errors: No known data errors is the pass. Anything else means running sudo zpool status -v tank, which prints the complete list of data errors since the last complete scrub, including the affected filenames. Restore those files from backup, run sudo zpool clear tank to reset the counters, then scrub again. Each complete scrub rebuilds that list, so a filename that is absent after a full clean scrub is genuinely gone.
Which periodic scrub job is on your box?
Do not assume there is one, and do not assume there is only one. The mechanism differs by platform and by package. The pool format is identical everywhere, which makes it easy to forget that the tooling around it is not, and the way ZFS ships on FreeBSD compared with Linux is the difference that matters here.
On FreeBSD the job lives in the periodic system. Set these in /etc/periodic.conf:
daily_scrub_zfs_enable="YES"
daily_scrub_zfs_pools="tank"
daily_scrub_zfs_default_threshold="35"daily_scrub_zfs_pools is a space separated list of pool names, and leaving it empty scrubs every pool. daily_scrub_zfs_default_threshold is the number of days between scrubs when no pool-specific threshold is set, and the manual gives 35 as the default. The daily job runs every day; it starts a scrub only once the threshold has passed.
On Linux it depends on your distribution's ZFS package, and some systems carry both mechanisms at once. There are per-pool systemd timers, zfs-scrub-monthly@tank.timer and zfs-scrub-weekly@tank.timer, enabled one pool at a time. Debian and Ubuntu also ship /etc/cron.d/zfsutils-linux, which runs a script that scrubs every ONLINE pool on the second Sunday of the month. Check what you have before adding anything:
systemctl list-timers 'zfs-*'
ls /etc/cron.d/ | grep -i zfs
sudo zpool history tank | grep scrubzpool history is the honest answer, because it records the scrubs the pool actually started, with dates. Two scrubs a month means both mechanisms are live and one of them should go. To enable a timer:
sudo systemctl enable --now zfs-scrub-monthly@tank.timerFree space matters more than any tunable
Scrub duration on a small pool is set by how much data is allocated and how scattered it is. Filling the pool makes both worse.
OpenZFS guidance is to keep pool free space above 10%. Below that, metaslabs, the chunks the allocator works in, start crossing a 4% free threshold, and the allocator switches from first-fit to best-fit. Best-fit is much more CPU intensive. Write latency rises, fragmentation follows, and the next scrub is slower still, because the same amount of data now arrives as more and smaller reads.
The first lever is therefore not a tunable. It is deleting things. Old snapshots are the usual cause on a ZFS box, followed by Docker images and layers nobody has pruned and kernel packages left behind by upgrades. Run zfs list -o space before you touch anything else, because it separates space held by snapshots from space held by live data.
Then the knobs, briefly. On Linux you can read the current values:
cat /sys/module/zfs/parameters/zfs_scrub_min_time_ms
cat /sys/module/zfs/parameters/zfs_vdev_scrub_max_activeFreeBSD exposes the same parameters through sysctl, so find yours with sysctl -a | grep scrub. Raising them finishes the scrub sooner and makes your application slower. Lowering them does the reverse. On a pool with one or two devices no setting gives you both, because there is only one queue to divide. A knob rarely fixes a design. If a monthly scrub hurts, the honest reading is that the pool is too full or the device is too slow, and a tunable only moves the problem.
Scrub time is your resilver preview
A resilver does the same walk as a scrub: it reads the allocated blocks, verifies them, and writes the missing ones to the replacement device. So the time your scrub takes is the closest honest preview of how long a rebuild will take, and of how long the pool will run with reduced redundancy while that happens.
ZFS schedules resilver work more aggressively than scrub work, so a rebuild usually finishes sooner than a scrub of the same pool. Treat your scrub time as a conservative upper bound. If the scrub takes nine hours, plan for a rebuild window of that order, and understand that a second device failure inside that window loses the pool. That is the practical argument for mirrored pairs instead of one wide raidz group, since raidz, the parity layout ZFS uses in place of RAID 5, rebuilds by reading every surviving device.
On a single-device pool there is no resilver at all. The device goes, and the pool goes with it. Your recovery time is your restore time, so measure the restore instead. A restore you have never run is not a recovery plan.
What changes when you rent the disk
On a VPS the block device is virtual. The hypervisor presents a volume, and underneath it there may be local NVMe, or a replicated network volume with parity of its own. Two consequences follow for scrubs.
First, the platform's redundancy is invisible to ZFS, and ZFS cannot use it. If the platform repairs a media error below you, ZFS never sees the problem. If the platform hands up a wrong block, ZFS catches it and then cannot fix it, because the good copy sits on the other side of that boundary.
Second, you usually cannot read SMART (self-monitoring, analysis and reporting technology) data for the device beneath a virtual disk, so the early warnings that disk health monitoring on a VPS depends on may not be available at all. The CKSUM counter from your scrubs becomes the main signal you own.
If you want ZFS to repair rather than only report, the pool needs more than one device inside the same instance, and that is a plan decision rather than a tuning one. Choosing a storage VPS over a regular VPS gets you the capacity, though whether it gets you two independent devices depends on the plan. Run lsblk and confirm before building a mirror on what turns out to be two slices of one volume. We rent Linux and FreeBSD servers, not a managed ZFS appliance, so the scrub schedule and the backups are yours to run. That is the trade: full control of the pool, full ownership of its maintenance.
FAQ
How often should I scrub a ZFS pool on a VPS?
Monthly suits most small pools, and it matches what the packages already do: a second-Sunday cron job on Debian and Ubuntu, and a 35 day default threshold in FreeBSD's periodic system. Weekly is reasonable only after you have timed a scrub and seen it finish quickly on an otherwise idle box. On a busy pool with one or two devices, weekly scrubbing spends real application I/O every week and returns only a few weeks of earlier warning.
Is scrubbing a single-disk ZFS pool pointless?
No, as long as you are clear about what it gives you. With no redundancy a scrub detects corruption and cannot repair it, apart from metadata, which ZFS keeps an extra copy of by default. What you get is a named list of damaged files in zpool status -v, early enough to restore them while a good copy still exists somewhere else. The right response is better backups, since the scrub tells you exactly which file to restore.
Can I pause a ZFS scrub and finish it later?
Yes. zpool scrub -p tank pauses it, and the pause state and progress are written to disk periodically, so the scrub stays paused across an export or a reboot. Run zpool scrub tank again to resume from the last checkpoint. Do not use zpool scrub -s tank for this: -s stops the scrub, and the next one starts again from the beginning.
Why is my ZFS scrub so slow, and can I speed it up?
Scrub time tracks allocated data and fragmentation, not disk capacity. A pool past 90% full is slow because metaslabs below 4% free push the allocator from first-fit to best-fit, and the fragmentation that follows turns the scrub into many small reads. Freeing space usually helps more than any tunable. You can raise zfs_scrub_min_time_ms or zfs_vdev_scrub_max_active to give the scrub a larger share of the queue, but on a one or two device pool that share comes straight out of your application.