README
¶
wslbak
English | 繁體中文
Scheduled backups of a WSL distro that do not stop it, a test restore after every backup, a notification when something fails, and a one-line restore.
Status: 0.2, early releases. An automated suite has made real backups, test restores and restores on 18 distros and three builds of Windows, but nobody has used wslbak day to day yet. Keep the backups you already have until it has proven itself on your PC. Differential backups are new in 0.2.0 and switched off unless you turn them on.

The same tour as a video. It is a recording of the commands as they ran, on a
PC where wslbak was installed from its npm package and a demo distro named wslbak-demo existed;
no text that the commands printed is changed. The drawing script adds the step titles and the cards
at the start and the end, types the commands at a drawn speed, shows long output a page at a time
and shortens waits. A question is put after the lines that were printed before it, as on a console;
the recorder receives the two separately. The recording was made with version 0.2.0. The tour
switches differential backups on to show one; without wslbak config --mode diff, every backup is a
full one.
Files inside WSL are not covered by OneDrive or most backup tools: they live in one virtual disk, and
when that disk is damaged or Windows is reinstalled, everything in it is gone. The usual answer is a
scheduled wsl --export, but wsl --export terminates the distro before it exports (it still does
in WSL 2.7), so your shells,
dev servers and containers die every time it runs. wslbak reads the distro from the inside instead,
while it keeps running:
> wslbak init
> wslbak config --mode diff
> wslbak run
Backing up wslbak-demo → D:\WSLBackup\wslbak-demo
A full backup this time: there is no full backup yet that a differential can build on
wrote 20261012T021909Z.tar.gz (91 MB, 2 s)
Test restore…
test restore passed: all 512 sampled files match (2 s)
Done.
(This is the run in the recording: a freshly installed Debian holding 270 MB. A distro you work in is larger and takes correspondingly longer. The second command is optional: it switches differential backups on, and the line about a full backup is there because of it.)
- The distro is never stopped. A root
tarinside the distro streams it out; WSL is not shut down and nothing is written to the distro's disk. - Every backup is test-restored, unless you switch that off. The archive is imported as a temporary distro, checked, and removed.
- Standard format. A full backup is a plain
.tar.gzthatwsl --importaccepts, with or without wslbak. - Differential backups, if you want them.
wslbak config --mode diffmakes a full backup now and then, and on the other days stores only what changed (how, and what it costs). - One command to set up: a daily task, how many backups to keep, where they go.
- Restore never overwrites. A whole distro comes back as a new distro; single files come back into a new folder.
Install
Requires Windows 10 or 11 and the Microsoft Store version of WSL (wsl --version must work; run
wsl --update if it does not). Windows 10 itself has not been tried yet; see
Limitations for what has.
With Node.js 18 or newer, from a Windows terminal or from inside WSL:
npm install -g wslbak
With Scoop:
scoop bucket add boring206 https://github.com/Boring206/scoop-bucket
scoop install wslbak
Without either: download wslbak-<version>-windows-x64.zip (or -arm64) from the
Releases page, unzip it anywhere, and run wslbak.exe
from there. Keep wslbak.exe and wslbakw.exe together.
In every case the executables are prebuilt, so Go is not needed. wslbak init copies them to a fixed
place of its own, so the download folder, the npm installation or the Scoop folder can change later without
breaking the schedule.
Updating. Install the new version the same way (npm install -g wslbak, scoop update wslbak,
or a new zip), then run a wslbak command once, for example wslbak status. The scheduled task runs
the copy in that fixed place, and the copy is brought up to date when the new version runs a
command; --version, --help and --dry-run do not do it. wslbak init is not needed again.
Getting started
wslbak init
init looks at your distros (one that is stopped is started for that), proposes a destination, and
then shows what it is about to set up — the program files, the settings file, the registry key, the
scheduled task and its exact command line — before asking Proceed? [y/N]. Nothing is set up until
you say yes: up to then init has only made its own folder %LOCALAPPDATA%\wslbak with a log and a
lock file in it and, where wslbak was set up before, brought the installed copy of the program up to
date. --dry-run shows the same plan and stops without creating even those. No administrator rights
are needed. With several distros it asks which one; --all sets up all of them.
By default backups go to <drive>:\WSLBackup on the internal drive with the most free space other
than the one holding the distro, so that they survive reinstalling Windows. A PC that has no such
drive gets WSLBackup in your user folder instead; that is on the same drive as the distro and does
not survive a reinstall that formats it, so give another place with --dest there. An external
drive or a NAS share also protects against the disk itself failing; init tells you when the
destination is on the same physical disk as the distro.
wslbak doctor checks the whole setup at any time and says how to fix what it finds.
Usage
wslbak init set up backups and the daily task
wslbak config show the settings; with options, change them
wslbak run back up now, test-restore or check, prune old backups
wslbak list list backups
wslbak files [id] [path] list what a backup holds in a folder
wslbak status schedule, last result, and whether the installed program is still there
wslbak doctor check the environment and settings, with a fix for most problems
wslbak verify [id] test-restore an existing backup again (default: the newest); a
differential is restored together with its full backup
wslbak restore [id] restore a backup as a new distro (default: the newest verified one)
wslbak uninstall remove the task and the installed program; backups are kept
-d, --distro <name> which distro (default: for init the only one that can be backed up,
for run all that are set up)
--all init: set up every distro that can be backed up
--dest <folder> init: where to store backups
--keep <count> init, config: how many of the newest verified backups to keep (default 7)
--keep-weekly <n> init, config: also keep one per week, for n weeks (default 0)
--keep-monthly <n> init, config: also keep one per month, for n months (default 0)
--at <HH:MM> init, config: time of the daily backup (default 03:00)
--webhook <url> init, config: also report failures to this URL; with config, off removes it
--notify <when> config: failure (default) or always
--verify <how> config: restore (default) or none
--exclude <pattern> config: exclude one more path pattern (repeatable)
--unexclude <pattern> config: stop excluding a pattern (repeatable)
--enable, --disable config: turn backups of one distro on or off
--private config: let only your account open the backup folder
--mode <how> config: full (default) or diff, see "Differential backups"
--full-every <days> config: with diff, how long one full backup is built on (default 30)
--full run: make a full backup this time
--no-verify run: skip the test restore this time
--find <text> files: list entries whose name or path contains this text
--name <name> restore: name of the restored distro
--to <folder> restore: where to put the restored distro
--path <path> restore: bring back only this file or folder (repeatable; needs --into)
--into <folder> restore: put those files into this folder inside the distro
-n, --dry-run init, config, run, restore, uninstall: only show what would be done
-y, --yes init, restore, uninstall: do not ask for confirmation
--lang <lang> interface language: en or zh-TW (or set WSLBAK_LANG)
--debug show details and timing of each step
-v, --version print the version
-h, --help print the usage
Exit codes: 0 success (also after run --no-verify); 1 a backup was written but its test
restore could not be carried out, files found nothing, or status, doctor or config found
something that needs attention; 2 failure; 3 another wslbak is already at work.
Inside WSL you can give Linux paths (--dest /mnt/d/WSLBackup); they are converted for you.
Restoring a whole distro
wslbak restore
restores the newest backup that passed its test restore (for a differential, its check); if none
has passed, it offers the newest one and says so. If a distro with the original name still
exists, the copy is called <name>-restored-<date>; pick your own with --name. An existing distro
is never overwritten, changed or removed, and the restored one is not started for you.
After reinstalling Windows, the wslbak that was installed on C: is gone too. You do not have to
install it again, and you need neither Node nor npm: on every backup run wslbak copies its own
program (wslbak.exe) and a note (README-RESTORE.txt) into the backup folder, next to the
backups. So as long as the backup folder is still there (on D: or an external drive, say), install
WSL and run the copy in that folder:
D:\WSLBackup\wslbak.exe restore
(D:\WSLBackup stands for your backup folder: the one init proposed, or the one you chose.)
Without wslbak at all, a full backup is an ordinary archive:
wsl --import Ubuntu-24.04 C:\WSL\Ubuntu-24.04 D:\WSLBackup\Ubuntu-24.04\20260115T030000Z.tar.gz --version 2
A distro imported by hand logs in as root; add [user] and default=<your user name> to its
/etc/wsl.conf to change that. wslbak restore sets the default user for you.
A differential backup (<id>.diff.tar.gz) cannot be imported like that: it holds only what changed.
wslbak restore puts it together with its full backup. Without wslbak, importing the full backup
brings back the day of the full backup, and the differential, itself a plain .tar.gz, can be
opened with any tar to take single files out of it.
Bringing back single files
Most of the time you do not need the whole distro, just the file you deleted yesterday.
wslbak files /home/me/project what is in that folder in the newest backup
wslbak files --find notes.md where is a file whose name contains this
wslbak files 20260114T030000Z /etc the same for an older backup
wslbak restore --path /home/me/project/notes.md --into /home/me/recovered
--path takes a file or a folder and can be repeated. The files go into the folder given by
--into, inside the distro the backup came from, with their full path recreated underneath:
/home/me/recovered/home/me/project/notes.md. That folder must not exist yet (the folder above it
must), or be empty, so nothing you have now is ever overwritten; move the files where you want them
afterwards. Owners, permissions, extended attributes and ACLs are preserved.
Without an id, files looks in the newest backup whether or not it was verified, while
restore --path takes the newest verified one, as restore does. Give both the same id when that
matters.
A differential backup shows, and gives back, the distro as it was on its day: what the differential holds, and from its full backup everything that had not changed or been deleted.
What is backed up
Everything on the distro's root file system, with ownership, permissions, extended attributes, file
capabilities, hard links and sparse files. Sockets are skipped; tar cannot store them. Left out by
default: what is in /tmp and /var/tmp and in the .cache folders of home directories, and WSL's
own /init. Windows drives under /mnt are not part of the distro and are never included.
wslbak config show the settings
wslbak config --exclude "/home/*/Downloads/*" leave something out
wslbak config --keep 3 --keep-weekly 4 --keep-monthly 6
wslbak config --at 02:30
wslbak config --mode diff differential backups (see below)
--excludetakes a path pattern inside the distro;*matches anything,/included. Quote it, so your shell does not expand the*first.--keepis the number of newest verified backups.--keep-weeklyand--keep-monthlyalso keep the newest backup of each of the last n weeks or months that have one (the current week and month count), so they add a few older copies. They do not make backups smaller. To use less space, exclude what can be downloaded again (wslbak doctormeasures the usual caches and prints the command for each), or switch to differential backups.--verify noneturns the test restore off; then the newest backups are kept regardless.--notify alwaysalso notifies on success, so that silence means the task is not running.- Run
wslbak init -d <other distro>to add another distro. One daily task covers all of them.
The settings are stored in %LOCALAPPDATA%\wslbak\config.json. Each backup is three files in
<dest>\<distro>\: <id>.tar.gz, <id>.json (size, SHA-256, warnings, the result of the test
restore) and <id>.idx.gz (the list of files, used by wslbak files). The id is the UTC time of
the backup. A differential backup has <id>.diff.tar.gz in the place of <id>.tar.gz, and a full
backup made while differential backups are on has a fourth file, <id>.dirs.gz (see below). Next
to the settings wslbak keeps state.json (the last results), wslbak.log and a
lock file; wslbak.exe and README-RESTORE.txt in <dest> are written again on every run.
Differential backups
As it comes, wslbak makes a full copy of the distro on every run. With
wslbak config --mode diff
it makes a full backup now and then, and on the other days a differential: the files whose content
or attributes changed since that full backup, and every directory. A differential depends on its
full backup and on nothing else, so a restore never needs more than two archives, and deleting one
differential does not affect another. wslbak config --mode full switches back.
How much smaller a differential is depends on how much changes between two backups. In the end-to-end test on the development PC, the full backup of a freshly installed Debian with the test files is 186 MB, and the differential made after the test had edited, added, renamed and deleted a few dozen files is 555 KB. A run in this mode first lists every file of the distro without reading any of them; with two million files that took 14 seconds, and the differential made there after three small changes is 100 KB, next to a full backup of 88 MB.
-
How long a full backup is built on. 30 days, or what you give with
--full-every <days>; the backup after that is a full one.wslbak run --fullmakes a full backup at any time. A differential that has grown to more than half the size of its full backup is also the last one on it. -
Restoring is the same.
restore,restore --path,filesandverifytake a differential like any backup. wslbak puts it together with its full backup: the differential's directories, then everything in the full backup that the differential did not replace and that had not been deleted, then the differential's files. Each path is written once, and a whole distro is still onewsl --import. -
What counts as changed is decided by GNU tar, from the two times the file system keeps for every file: when its content was last modified, and when anything about it last changed (its name, mode, owner, links). A file that changed at all is stored again as a whole.
-
A differential is only kept when every file can be accounted for. After tar has written it, wslbak goes through a list of everything in the distro: each file must be in the differential, or be in the full backup at the same path, in a folder that is still the same folder (the same inode as recorded with the full backup). If that holds, the backup's record says which entries of the full backup this restore point no longer wants. If it does not, the differential is thrown away and that run makes a full backup instead, and tells you why:
- an old folder was renamed or moved, or two folders changed places. The files in it did not change, so tar does not pick them up, and they are not where the full backup has them. (A renamed file is no problem, and neither is a new folder.)
- while the backup ran, a file disappeared from a folder that the full backup has no record of. Most likely that is a new folder with a short-lived file in it, but it could be an old folder that was moved in from a path the full backup leaves out (an excluded one), and the two cannot be told apart;
- while the backup ran, an old folder disappeared and a new one appeared. That may have been a rename, and then the unchanged files in it would be in neither archive;
- a folder of the distro is mounted at a second place inside it, so everything in it is seen twice;
- the exclude list changed, or the distro's tar is another version;
- the distro's clock is earlier than it was at the full backup, or a folder carries a change time from before the full backup that it did not have then;
- the root file system is not ext4;
- the full backup failed its test restore, or the last differential failed its check.
wslbak runprints the reason. A scheduled run has no window, sowslbak statussays whether the last backup was a differential and, if it was a full one, why. Some reasons do not go away by themselves (the file system, a folder mounted twice); with those, differential backups are in effect off for that distro. -
Checking. A full backup is test-restored after every run, as always. A differential is read back after every run (it unpacks, its checksum and its number of entries are what was written, its full backup is in place with the size it had), and is test-restored together with its full backup for the first differential of each full backup and again whenever the last such test is seven days old.
wslbak verify <id>does the real test restore whenever you ask. Switching the test restore off switches the read-back off with it: with--verify none, and for a run with--no-verify, a differential is not checked at all. And while test restores are on, a full backup that was made with--no-verifyis not built on; the next run makes another full backup. -
Keeping.
--keepand the weekly and monthly rules count every backup, full or differential, as one. A full backup is never deleted while a differential that passed its check still needs it, so a few more files than--keepsays can be there. -
What it writes inside the distro. To know what is there now, tar first walks the whole tree without opening any file and leaves a list of the names. That list is a temporary file under
/run(or/dev/shmwhere/runis not kept in memory), so not on the distro's disk; only root can read it, and it is deleted as soon as wslbak has read it. A run makes one to three such lists, one at a time. A full backup made in this mode keeps the part of the list it needs later, the inode of every directory, as<id>.dirs.gz.
The test restore
After a backup is written, wslbak imports it with wsl --import as a temporary distro, runs a check
inside it, and unregisters it again. The check confirms that the default user and their home
directory exist (when the default user is not root), that 512 files, picked at random while the backup was being read, have the same
SHA-256 as they had then, and that the eight largest files have the size they had (too big to hash
every time, and the ones an importer is most likely to get wrong). Before that, the whole archive
is read back and compared with the SHA-256 recorded when it was written. If the importer complains
that it could not read part of the archive, the test restore fails even though WSL reports success.
The temporary distro must not come alive: all WSL2 distros share one network namespace, so a faithful
copy that boots would start a second set of your services and scheduled jobs, with your credentials.
wslbak therefore appends its own /etc/wsl.conf to the end of the stream it imports (the archive on
disk is not altered) that turns off systemd, boot commands, Windows drive mounts and interop, and it
refuses to start the copy unless that file is the one that ended up in place.
Six files are left out of the comparison: /etc/hostname, /etc/hosts, /etc/resolv.conf and
/etc/ld.so.cache, which WSL writes itself when it starts a distro, and /etc/wsl.conf and
/etc/wsl-distribution.conf, which wslbak replaces in the copy. WSL writes the host name into the
copy as well, and there it is the PC's name, whatever the distro's own wsl.conf asks for. Where
/etc/hostname, /etc/hosts or /etc/resolv.conf is a symbolic link (NixOS keeps /etc/hostname
in /nix/store), the file behind the link may differ too without the test restore failing.
WSL makes a Start Menu folder for every distro it imports and does not always take it away again; wslbak removes the empty one that its temporary distro leaves behind.
Only verified backups count towards --keep (a differential counts when it passed its check, see
Differential backups). Backups that could not be verified are kept
separately, at most two, so a run of failures never pushes out your good backups.
Notifications
A failed or unverified backup raises a Windows notification. --webhook <url> adds a second channel:
ntfy topics get a plain-text message, Discord and Slack webhook URLs get their own
JSON format. The URL is treated as a secret: only its host name is shown or logged (a value that is
not a URL at all is shown back to you in the error message). Notifications say
what kind of problem occurred but never include file names; the details stay in the log on your PC.
A scheduled run that finds another wslbak still there after 20 hours raises a notification too, because one that is stuck, or a window left waiting at a question, cannot (see Safeguards).
wslbak status exits 1 and says so when the last success is more than two days old, when the task is
missing, or when the installed program has disappeared.
Safeguards
- wslbak never calls
wsl --shutdown,wsl --terminateorwsl --unregisteron your distros. The one place that unregisters anything only accepts a temporary distro whose name wslbak generated, that is registered at exactly its own folder under%LOCALAPPDATA%\wslbak\verify, and that it left a claim file for. A test checks that no other code path can reach that call. - Old backups are deleted one file at a time, and only files that have a wslbak manifest next to them.
Other files in the backup folder are left alone, even ones that look like backups. Nothing is
deleted in a folder that holds backups written by a newer version of wslbak. The one exception is
wslbak's own unfinished files,
<id>.tar.gz.partialand the like: what an interrupted run left is removed by the next run. - A full backup is not deleted while a differential that passed its check depends on it.
restorerefuses a name that is in use and a folder that is not empty, for distros and for files.- The scripts that run inside your distro never delete anything of yours, and use nothing beyond the
shell, coreutils and tar. (With differential backups on, the backup script removes the
temporary file it wrote itself for each list of names.) What you type for
--path,--intoand--excludenever becomes part of a command line that a shell interprets:--pathdoes not enter the distro at all, and the other two arrive as data and are only handed on as single arguments (totar --exclude=…, and to the commands that check and fill the target folder). - File names from inside the distro are shown with control characters made visible, in listings and in the warnings of a backup, so a file with a crafted name cannot send commands to your terminal.
- Single files only go back into the distro the backup folder belongs to, whatever the records in that folder say. They are unpacked in a folder that only root can enter and moved into place at the end (also when unpacking stopped part-way, so that you can see what did come back), so a user inside the distro who owns the folder above the target cannot redirect them by swapping the target for a link.
- Distros managed by another program (
docker-desktop*,rancher-desktop*,podman-machine-*) and WSL1 distros are refused with a reason. - Only one wslbak changes things at a time. A second
init,run,verify,configchange,restore --pathoruninstallexits with code 3 (a scheduled run that finds another one at work simply ends). Commands that only read, and restoring a whole distro as a new one, are not held back. - A wslbak that never ends does not go unnoticed. When a scheduled run finds that another wslbak has
held things up for 20 hours or more, it does not simply end: it fails with exit code 2 and
notifies you, because the one that is stuck cannot do that itself. This is for a wslbak that was
started by hand, a window left waiting at a question included. A run started by the schedule
cannot last that long: Windows ends it after 12 hours, which is the task's time limit, and
nothing is reported then. What shows it is how long ago the last success was, in
wslbak status, which warns once that is two days. - A WSL that has stopped answering does not leave wslbak waiting for ever. Every WSL command has a time limit. For the backup itself it is ten minutes without any data arriving, counted from the moment the command starts, so also when not even the beginning of the archive comes; for the pass that lists the names in differential mode it is an hour in all. When a limit runs out, wslbak ends the command. A backup that ran out of time is reported as failed; a list that did is followed by a full backup instead of a differential, which makes its own list first. So in differential mode the limits add up: a WSL that does not answer at all is reported after a little over two hours, not after ten minutes. If the command cannot even be ended, wslbak stops waiting for it about half a minute later and carries on in the same way. That last case cannot be produced on demand, so only its logic is tested.
How it works
wsl.exe -d <distro> -u root -e sh -s runs a small script inside the distro. The script runs GNU tar
on / with --one-file-system and writes the raw archive to stdout. On the Windows side wslbak
compresses it with parallel gzip, hashes it, and writes <id>.tar.gz.partial, which is renamed only
when tar ended without an error that matters and the number of bytes received equals the number tar
says it wrote. (Files that changed or disappeared while they were read are normal on a running
system; they are reported as warnings.)
One field of tar's output is filled in on the way. For a file of 8 GiB or more, GNU tar records the
size only in an extended header and leaves the size in the file's own header at zero. The importer
that ships with WSL 2.7 (bsdtar 3.7.7) believes the zero: such a file comes back empty, and
wsl --import still reports success. wslbak writes the real size into that header as well (and
corrects the header's checksum to match), so the archive restores correctly there too. Nothing else
is changed, and the archive stays a valid tar.
With differential backups on, the same script is run in two more ways. First with tar writing to
/dev/null and --listed-incremental: tar then opens no file and only records, in a snapshot
file, every directory with its inode and the names in it. Then, for a differential, with
--after-date and the time of the full backup. The snapshot option is used for that listing only;
the archives themselves are never in tar's incremental format, which could not be restored without
deleting and renaming things inside a distro.
The scheduled task belongs to your Windows account and runs with your sign-in session. It starts a
windowless copy of the program kept in %LOCALAPPDATA%\Programs\wslbak, so it does not depend on Node
or on anything inside a distro. The task is set to run as soon as possible after a missed start and
again ten minutes after you sign in (where Windows does not let your account create the sign-in
trigger, only the daily start is set); the program then skips the run if a backup succeeded within
the last 20 hours.
Limitations
- Files that are being written during the backup may be inconsistent in it. There is no snapshot. Databases should be dumped to a file by their own tools; the dump is then backed up reliably.
- POSIX ACLs are not restored when a whole distro is restored. They are stored in the archive,
but
wsl --importdoes not apply them.wslbak runtells you how many files are affected (systemd's journal folders, which are reset at boot, are not counted). Bringing back single files with--pathdoes preserve them. - Backups are not encrypted. A backup holds every file of the distro, private keys and password
hashes included. Whoever can read the backup folder can read all of it, and whoever can write to
it can alter a backup or the copy of the program kept there.
wslbak doctortells you when other accounts on the PC can read the folder, andwslbak config --privaterestricts it to your account (plus SYSTEM and Administrators). That is not done by default because of what it costs later: after reinstalling Windows your new account is not on the list, and you have to open the folder in Explorer and confirm its question, or use an administrator's terminal, before you can restore. Only verify or restore backups from a folder that nobody else can write to: a test restore runs programs that come out of the backup. - Differential backups have limits of their own, besides being new:
- They trust the distro's clock. tar picks the files whose times are later than the full backup (less five minutes). If the clock is set back between two backups and a file changes during that time, its times can be earlier than the full backup, and the file is missed until the next full backup. wslbak makes a full backup when it notices: when the clock is earlier than at the full backup, and when a folder carries a change time from before the full backup that it did not have then. A file rewritten in place while the clock was back, in a folder where nothing else happened, is not noticed.
- Only on ext4, the file system WSL gives a distro. Elsewhere every backup is a full one.
- After an old folder was renamed or moved, the next backup is a full one, however little else changed.
- A file that changed at all is stored again as a whole, a large database or disk image included.
- A differential is worth nothing without its full backup. If the full backup is damaged or
deleted by hand, its differentials cannot be restored as a distro.
listmarks them once the full backup's files are gone; damage to its archive shows atverify, atrestore, or at the next test restore of the two together. Single files can still be taken out of such a differential's archive with tar, but do it soon: the next run keeps at most the newest two of them. - Things that happen while a differential is being made are caught as far as they can be told from what tar saw (see the reasons for a full backup above). One case is not: a folder without sub-folders that is moved in from an excluded path during those minutes is an empty folder in that one restore point. The next run notices it and makes a full backup.
restore --pathwith a folder that has very many sub-folders is slower from a differential restore point than from a full backup. The folders arrive before their files there, so tar has to set their times at the end, and the GNU tar of current distros (1.34 and 1.35, not 1.30 and older) takes time for that which grows with the square of their number: on this PC 3 seconds for 40,000 folders and 12 seconds for 80,000.
- GNU tar is required inside the distro. A distro with BusyBox tar (Alpine as it comes) or with no tar (openSUSE Tumbleweed as it comes) is refused, with the command that installs it.
- Only what is on the root file system. A folder mounted from another disk is skipped.
wslbak runnames it when it holds a Linux file system (ext4, xfs, btrfs and the like) and is mounted outside/mnt/wsl; network shares, FAT and NTFS drives and disks attached withwsl --mountare skipped without a message. - The test restore needs free space on the drive holding
%LOCALAPPDATA%, up to about the size of the distro's files. When there is not enough, the backup is kept, reported as not verified, and the exit code is 1. - The task runs only while you are signed in. A stopped distro is started for the backup.
- The test restore is a sample. It proves the archive imports, that 512 files are intact and that the largest files have the right size, not that every byte of every file is.
- Single files can only be brought back into the distro, not straight into a Windows folder. From
Windows, open the result at
\\wsl.localhost\<distro>\<folder>. - Where it has been tested. Developed on Ubuntu 26.04. The end-to-end suite passes against
Debian 12 and 13, Ubuntu 20.04, 22.04 and 24.04, Fedora 44, AlmaLinux 8 and 9, Rocky Linux 9 (the
build from CIQ that
wsl --installoffers), Oracle Linux 7, 8 and 9, Arch Linux, openSUSE Tumbleweed, Kali, Gentoo, NixOS and Alpine 3.24 (GNU tar 1.26 to 1.35); with WSL 2.7 and 3.0; on Windows 11 (build 26300), Windows Server 2025 (build 26100) and Windows Server 2022 (build 20348, the generation of Windows 10); and with Avast and with Microsoft Defender's real-time protection switched on, each with a caveat. The checks of differential backups came with 0.2.0 and have run against nine of those distros (Debian 13, Ubuntu 22.04, Fedora 44, AlmaLinux 8, Oracle Linux 7, Arch Linux, openSUSE Tumbleweed, NixOS and Alpine 3.24; GNU tar 1.26, 1.30, 1.34 and 1.35), with both versions of WSL and on all three versions of Windows; the other distros were last tested with 0.1.x. With Avast the suite has passed many times, but see under Troubleshooting what its sandbox did. Defender is switched on in the middle of a job on GitHub's test machines, which otherwise run without it. Of 65 such runs of the suite, 59 passed; in one, a scheduled backup was written but not recorded as a success, and the log that would say why was not kept; and 5 ended because the whole machine froze (it stopped answering and then lost its connection), two of them before wslbak had started. The freezing is put down to those machines, not to wslbak: of 54 machines of the same kind that only set WSL up, with no wslbak on them, one froze as well, while 108 machines that switched Defender on and did not use WSL all kept running. With 0.2.0 the suite has passed there every time it ran. The arm64 executables start and pass the unit tests on arm64 Windows, where no WSL was available to back up. Not tried yet: Windows 10 itself, a PC with Docker Desktop, and distros of 100 GB or more; reports are welcome, as issues.
Troubleshooting
Start with wslbak doctor.
- Where is the log?
%LOCALAPPDATA%\wslbak\wslbak.log. It is in English, except for the messages that are also shown to you, which are in your interface language. Add--debugto see it live. - Windows refuses to start the program. The executables are not code-signed. Smart App Control blocks unsigned programs outright, and wslbak cannot run while it is on; AppLocker or WDAC policies can do the same on managed PCs.
- Antivirus holds the program, or runs a second copy of it. Some products (Avast and AVG, for
example) check a program they have never seen before for a while, or run it in a sandbox, and
inside a sandbox wslbak cannot talk to WSL.
inittherefore starts the installed program once while you are there. On a PC with Avast, with executables that had just been built there, two more things were seen. Avast ran an isolated second copy at every start of the program; that copy failed and sent a failure notification of its own, although the real backup had succeeded and neitherwslbak statusnor the log knew of any failure. And twice, while Avast was doing this, WSL stopped starting Windows programs altogether untilwsl --shutdown. Whether Avast treats the released executables the same way is not known. If scheduled backups never run, or you get failure notifications thatwslbak statusknows nothing about, add%LOCALAPPDATA%\Programs\wslbakto the antivirus exceptions. Do not turn the antivirus off. statussays the installed program is missing. Antivirus software may have quarantined it. Restore it from the antivirus history, then runwslbak initagain to put the files back.- "Cannot write to …" during init. With Controlled folder access on, allow the program in Windows Security or choose a folder that is not protected.
.exefiles stop working inside WSL ("Exec format error") after another distro stops. This is not caused by wslbak: some distros clear a kernel setting that all your distros share when they shut down. It has been seen with Fedora 44, openSUSE Tumbleweed and Rocky Linux 9. It can show up after a backup because a backup starts a stopped distro, which then stops again.wsl --shutdownfixes it, or, without restarting, from a Windows terminal, with the name of the distro you are working in:wsl -d <distro> -u root sh -c "echo ':WSLInterop:M::MZ::/init:P' > /proc/sys/fs/binfmt_misc/register". (Without-dthe command runs in the default distro. If that one is not running, it is started and stops again, which can clear the setting once more.)- "cannot start Windows programs right now" inside WSL. wslbak looks at the kernel setting
that hands
.exefiles to Windows before it starts its program, and it is missing or switched off. In a WSL 2 distro a missing one is the case above, and the same command puts it back;wsl --shutdownrestores it in either case. In a WSL 1 distro the setting is missing when/etc/wsl.confhas[interop] enabled=false. - Inside a WSL 2 distro, one line of error from WSL after about ten seconds, and nothing else.
This is what happens when Windows interop is switched off for the distro (
[interop] enabled=falsein/etc/wsl.conf). In WSL 2 the kernel setting stays in place then, so wslbak's check passes, and WSL itself fails to start any Windows program and prints its own error. With WSL 2.7.13 that wasERROR: UtilAcceptVsock:273: accept4 failed 110after ten seconds, and the exit code was 1 although wslbak's program never ran: that 1 comes from WSL and has none of the meanings listed under exit codes above. Switch interop back on, or use wslbak from Windows. - In Git Bash,
--path,--intoandfiles <path>are refused as Windows paths. Git Bash rewrites arguments that look like Linux paths (/home/mebecomesC:/Program Files/Git/home/me) before wslbak sees them; wslbak notices and says so. PutMSYS_NO_PATHCONV=1in front of the command, or use PowerShell, cmd or a WSL shell.
Questions and problems
Found a bug, or something this page does not explain? Open an issue at
https://github.com/Boring206/wslbak/issues. It helps most to include the output of
wslbak doctor, the version (wslbak --version) and the relevant lines of
%LOCALAPPDATA%\wslbak\wslbak.log. For anything that may be a security problem, please use the
private form that SECURITY.md
describes, not a public issue.
Development
Requires Go 1.26 or newer and Node.js. See CONTRIBUTING.md for the rules the code keeps to, docs/ARCHITECTURE.md for how it is put together, and SECURITY.md for reporting a vulnerability.
npm test # go vet plus unit tests
npm run build # builds the four executables in bin/ (and a helper for the tests)
npm run e2e # end-to-end tests inside WSL, against a throwaway distro and a sandbox folder
npm run dist # release zips, checksums, and winget and scoop manifests in dist/
bash scripts/demo.sh makes the tour at the top of this page again: it installs the packed npm
package and a demo distro, runs the commands for real (the real settings folder, scheduled task
and default folders, so it refuses to start on a PC where wslbak is set up), records what they
print, draws the recording and removes what it created. It needs npm run build to have run,
python3 inside WSL, a Python with Pillow on Windows and, for the video file, ffmpeg. It takes a
quarter of an hour, six minutes of which it waits: the demo distro has to be older than the five
minutes a differential reaches back before its full backup.
npm run e2e creates a Debian distro named wslbak-e2e-<random> on first use; set
KEEP_E2E_DISTRO=1 to keep it for the next run, and remove it with scripts/e2e-distro.sh destroy.
Set E2E_DISTRO to test another family (FedoraLinux-44, archlinux, openSUSE-Tumbleweed,
alpine, …), or E2E_ROOTFS_URL to import a root file system that wsl --install does not offer.
E2E_FAST=1 leaves out the parts that only exercise Windows, and E2E_TOAST=1 adds a check that
shows a real notification. Three more scripts are run by hand: scripts/e2e-scale.sh (millions of
files, a file over 8 GiB), scripts/e2e-services.sh (a busy Docker Engine and SQLite databases
that are being written) and scripts/e2e-tarfacts.sh (what differential backups take for granted
about GNU tar, ext4 and wsl --import; it needs python3). The
tests never touch another distro or your real wslbak settings.
The tests reach a few situations through switches that only work together with the sandbox option
--home: the WSLBAK_TEST_… environment variables in testhooks.go. They do nothing in normal use. When developing inside WSL without Go installed there, the
build script falls back to go.exe on Windows; set the GO environment variable to point somewhere
else.
The program's interface text lives in i18n.go, once per language. Add or change both when you
touch a message; the tests check that nothing is missing. (The npm launcher, bin/wslbak.js, has a
few messages of its own.)