Restoring a Linux Server: What the Backup Kept and the Restore Threw Away

ByLinuxtastic

September 4, 2026

Restore a tree with rsync -aH, the flags most backup scripts are built on, and check it the way nearly every guide says, including ours: diff -r against the original. It exits 0. An ACL is gone, an extended attribute is gone, a file capability is gone, and a directory that was thirty-six kilobytes is now two hundred and fifty-seven megabytes:

                         live                      restored
  owner:group and mode   identical                 identical
  named ACL entry        user:www-data:r--         (none)
  user.deployed xattr    2026-08-31                (none)
  helper capability      cap_net_bind_service=ep   (none)
  store.img blocks       0                         524288
  du -sh of the tree     36K                       257M

  $ diff /tmp/restore-test/index.html live/index.html
  identical
  $ diff -r /tmp/restore-test/ live/ ; echo "exit=$?"
  exit=0

diff answers a question about bytes, and every one of those losses is about something that is not a byte. Almost nothing here was missing from the archive. The capability bytes are in the tar file; an unprivileged nightly cron records other users’ ownership and the setuid bit perfectly; restic carries ACLs, extended attributes, capabilities and hard links with no flags at all. What throws half of it away is the restore: a flag on the extraction side, the account the command ran as, and a file on the target machine that was never in the backup.

One belief to settle first, because it is almost universally held backwards. When you copy files preserving ownership, it is the name that travels, not the number. Both tar and rsync put name and number in the archive and resolve by name by default; --numeric-owner and --numeric-ids make the numbers travel, and almost nobody passes them. What ACLs and capabilities are is Permissions and Privilege, Properly‘s; this page is the hour in which a backup becomes a working machine.

A restore has its own order, and it is not the backup’s order reversed. Seven stages, each deciding something the backup did not: 1. what the archive recorded, and what it left you to decide · 2. identity — the names in the archive against the numbers on the new machine · 3. the account that ran the restore, and its umask · 4. the metadata beside the bytes · 5. the shape of the file: links, holes, and the things that are not files · 6. what the walk could not see · 7. the other copy, and everyone who cannot tell them apart.

The hinge: compare the two things you are holding, with a tool that can see more than bytes. A restore is the one moment when both halves are in front of you. Every command exited 0 and diff says they are identical; ask something that sees further:

rsync -n -i -aHAX --checksum /path/to/the/backup/ /path/to/the/restored/tree/

-n changes nothing. Read which letter appears, not how many lines there are. Each column is one attribute, and each attribute is a reference to something that is not in the file:

What you seeWhat differsWho owns the answerStage
>f+++++++++on one side and not the otherthe exclude list somebody wrote months ago1
.f....og...owner or groupthe target’s /etc/passwd, written by the package manager in whatever order it installed2
.f...p.....the mode bitsthe umask of the account that ran the restore3
.f.......a.an ACLwhoever set it, and the flag the extraction did not pass4
.f........xan extended attributethe kernel — it reads security.capability, and it refused to let an unprivileged restore write it4
hf … => …one side is a hard link, the other a copythe filesystem’s link count5
.f..t......the timestampthe clock on the machine that did the restoring6
nothing at allnothing rsync can compare differs — the branch the page exists foreverybody who is not on this machine6 and 7

The silence is the important branch, and it has two causes. Either the restore carried everything rsync can carry — in which case what is left is stages 6 and 7, none of which is inside the tree. Or the backup never had it either, and you have compared a copy with a copy of the copy. One command separates them, and no output means the loss happened at stage 1, months ago, on a machine you may no longer have:

getfattr -Rd -m 'security|system' /path/to/the/backup/ 2>/dev/null | head

Two limits, both measured. If your backup is a tar archive rather than a tree, the nearest equivalent is tar -df <archive> -C <tree>, and even with --acls --xattrs --xattrs-include='*' its output is byte-for-byte identical with the attribute flags and without them: tar’s verification cannot see the attributes tar’s own flags exist to carry. It answers stages 1, 2, 3, 5 and 6 and hands stage 4 to getfacl -R and getcap -r. And the hinge compares the backup with the restore; it says nothing about whether the backup was true.

Stage 1: What the archive recorded, and what it left you to decide

One tar archive, two files owned by two service accounts, listed twice:

$ tar -tvf plain.tar
-rw-r--r-- metrics/metrics   9 2026-09-04 17:48 ./metrics.db
-rw------- appsvc/appsvc     7 2026-09-04 17:48 ./app.conf

$ tar -tvf plain.tar --numeric-owner        # the same archive
-rw-r--r-- 1600/1600         9 2026-09-04 17:48 ./metrics.db
-rw------- 1500/1500         7 2026-09-04 17:48 ./app.conf

The ustar header carries both. The column you see is decided by the flags you typed, so listing the archive to see whether it says names or numbers reads its own input.

The same holds for the attribute everybody believes is absent. tar --xattrs writes the file capability into the archive; the bytes are findable with grep -a 'SCHILY.xattr.security.capability'. It does not come back, and stage 4 is why. Three things really are absent, all stages 5 and 6: what was never a file, what was moving while the walk went past, and sockets. Everything else on this page was in the backup.

Verify: run getfattr -Rd -m 'security|system' over the backup, not the machine. If it prints nothing, stop reading about restores and fix the backup.

Stage 2: The names in the archive, and the numbers on the new machine

A service account’s number is whatever was next when the package was installed, on the machine where it was installed. Install the same packages in a different order on the rebuilt box and the same names hold different numbers, with nothing recording it. The same archive, restored three ways onto a target where two service accounts hold each other’s numbers:

# source: appsvc=1500 metrics=1600      target: appsvc=1600 metrics=1500

--- plain.tar, default flags (tar matches by NAME) ---
-rw------- 1 appsvc  appsvc  7 app.conf     # right owner, different number
-rw-r--r-- 1 metrics metrics 9 metrics.db

--- the same archive with --numeric-owner ---
-rw------- 1 metrics metrics 7 app.conf     # right number, wrong owner
-rw-r--r-- 1 appsvc  appsvc  9 metrics.db

--- default flags, names absent from the target altogether ---
-rw------- 1 1500 1500 7 app.conf           # falls back to the number, silently
-rw-r--r-- 1 1600 1600 9 metrics.db

Three outcomes, all three exit 0, none warned, and the default, the one you get by not thinking about it, is the one that keeps the ownership meaningful. The name is the durable identifier; the number is the local accident. rsync is the same: the name is on the wire, and --numeric-ids takes it off.

Stage 2’s own question: does the number on the restored file resolve, and to whom? find‘s %u prints the user’s name — or the bare number, if that number has no name here. Read which kind of token comes back, not its value.

find /srv /home /var/www -xdev -printf '%u %g\n' 2>/dev/null | sort | uniq -c | sort -rn
The rows showWho owns the answerStage
digitsnobody — the number is unclaimed, and the next useradd -r will take it2
a service account’s namethe target’s /etc/passwd, and the order the packages were installed in2
root root and nothing elseeither the source really was all-root, or an archive whose ownership was flattened before it got here1
one non-root name, everywherethe account the restore ran as3

Half of the identity files

/etc/passwd maps name to number; /etc/shadow is keyed by name alone and holds no number. Restoring one without the other does not fail. Three users on a rebuilt machine whose numbers were allocated in a different order, old passwd restored over the new one:

$ ls -ldn /home/*                     $ ls -ld /home/*
drwxr-x--- 2 3002 3002 /home/alice    drwxr-x--- 2 bob   alice /home/alice
drwxr-x--- 2 3003 3003 /home/bob      drwxr-x--- 2 carol bob   /home/bob
drwxr-x--- 2 3001 3001 /home/carol    drwxr-x--- 2 alice carol /home/carol

Sensible modes, every owner wrong and every group right. Everybody can log in; nobody can read their own mail. The other half-restore, old shadow over new passwd, is quieter: everyone authenticates with their old password and the file ownership on disk is what is broken. pwck prints no changes and exits 0, because it checks those files against each other, not against the filesystem. Restore passwd, shadow, group and gshadow together or not at all.

So resist the reflex to follow every restore with chown -R: the default already mapped the names, and the recursive chmod that usually follows lowers ACL masks. Restore as root, keep the names, then ask find which numbers failed to resolve.

Verify: run the find … -printf '%u %g\n' line above on the restored machine. Every row that is digits rather than a name is a file with no owner, and a number waiting to be reissued.

Stage 3: The account that ran the restore

Every guide is careful about which account takes the backup; almost none says which account performs the restore. The restoring account rewrites two things and cannot write a third. Ownership: only a privileged process can give a file away; everybody else gets their own, on every file, with no message, and the same account cannot write security.capability or create device nodes, both skipped rather than refused. The mode, through the umask: GNU tar’s -p is the default for root and not for anybody else, so non-root extraction subtracts the umask from every mode in the archive:

archive contains:   -rwsr-xr-x root/root  suidprog

umask 022, no -p    -rwxr-xr-x backupop backupop   # setuid silently dropped
umask 077, no -p    -rwx------ backupop backupop   # and every other file 0600
umask 077, with -p  -rwsr-xr-x backupop backupop   # a setuid-BACKUPOP binary
restored by root    -rwsr-xr-x root     root       # correct

Read the third row twice. -p as a non-root user does not restore a setuid-root binary; it creates a setuid binary owned by whoever ran the restore. If that is a deploy account with a key on three other machines, the restore has manufactured a privilege escalation out of a backup, and every command in it exited 0.

Partial success is the dangerous shape, on the recipe this site recommends. Automated Backups says to give the remote backup account only what it needs. Here is that account backing up /etc/ssh:

rsync: [sender] send_files failed to open "/etc/ssh/ssh_host_ed25519_key": Permission denied (13)
rsync error: some files/attrs were not transferred (see previous errors) (code 23)

$ ls /backup/etc/ssh/
moduli  ssh_config  sshd_config  ssh_host_ecdsa_key.pub
ssh_host_ed25519_key.pub  ssh_host_rsa_key.pub

Three public host keys copied, three private ones omitted, and the directory looks populated. The signal is exit 23 in a nightly cron job that has been “working” for a year: some files were not transferred, the same ones every night.

Verify: id, before you type the restore command. Then check the backup job’s recorded exit status, not its log. If it is 23, the two commonest omissions are private keys and device nodes.

Stage 4: The metadata beside the bytes

Beside a file’s contents sit its ACL, its extended attributes, its capabilities and, on a labelled system, its SELinux context. Every tool has to be told to carry them, and the flags are asymmetric. A binary with cap_net_bind_service, copied five ways and then run:

as deployed:  bind80  cap_net_bind_service=ep
run as an ordinary user:  listening on 127.0.0.1:80   exit=0

(a) tar -cf / tar -xf                          cap: lost   bind(127.0.0.1:80) failed:
                                                           Permission denied (errno 13)
(b) tar --acls --xattrs --selinux, both ends   cap: LOST   Permission denied (errno 13)
(c) tar … --xattrs-include='*' on EXTRACT      cap: kept   listening on 127.0.0.1:80
(d) rsync -aH  (the usual backup flags)        cap: lost   Permission denied (errno 13)
(e) rsync -aHAX                                cap: kept   listening on 127.0.0.1:80

every one of those five restores exited 0

Row (b) is the finding. tar --acls --xattrs --selinux on both ends restores the ACL, restores the user.* attribute, and loses every file capability on the machine. The bytes are in the archive; GNU tar’s manual says why, and says it as a feature:

By default, when --xattrs is used, all names are stored in the archive (with --create), but only user namespace is extracted (if using --extract).

extracting those (often security related) attributes on a different system than originally archived can lead to extraction failures, or even misinterpretations.

GNU tar declines to restore security attributes onto a different system, which is the definition of a restore. The override lives on the extract: --xattrs-include='*'. Red Hat has had this open since 2012, and Relax-and-Recover, a bare-metal restore product, shipped the wrong flags for thirteen months. SELinux labels live in the same security.* namespace and follow the same rule; that is documented rather than measured here, on an AppArmor machine with no label to lose.

The preservation matrix

Eight round trips, one tree, measured on GNU tar 1.35, rsync 3.2.7, coreutils 9.4 and restic 0.16.4:

Round tripOwner by nameMode + setuidACLxattr + capabilityHard linksSparseness
tar --acls --xattrs --selinuxyesyesyesuser.* only — capability lostyesno
tar + --xattrs-include='*' on extractyesyesyesyesyeswith -S
rsync -ayesyesnononono
rsync -aHyesyesnonoyesno
rsync -aHAX --sparseyesyesyesyesyesyes
cp -pyesyesyesnon/ayes
cp --preserve=xattrnononoyesn/ayes
resticyesyesyesyesyesno — needs restore --sparse

Two rows invert what the flag names suggest. cp -p preserves an ACL and cp --preserve=xattr does not: ACLs travel with the mode in coreutils. rsync -aH is what Automated Backups prints: right for a --link-dest snapshot tree, and two letters short of carrying the attributes. bsdtar restores security.capability from GNU tar’s own archive with no extra flags.

Verify: getcap -r /usr /opt /srv 2>/dev/null on the restored machine, and the same on the original while you still have it. Any binary on one list and not the other is a service that will fail at a future restart with a permission error on a file whose mode bits are correct.

Stage 5: Links, holes, and the things that are not files

Sparseness. A file with a declared size of one gibibyte and four kilobytes of data occupies four kilobytes. Nothing about that is in the file; it is the filesystem’s arrangement of it, and every tool must be told to look. One directory that du reports as 21M:

tar -cf     1,094,717,440 bytes     # about fifty times the disk it came from
tar -cSf       20,981,760 bytes     # bsdtar needs no flag and matches this

restored, du of the tree:
  tar -xf 1.1G     rsync -a 1.1G     rsync -aH 1.1G
  tar -cSf 21M     rsync -aH --sparse 21M     cp -a 21M     cp -r 81M

rsync -aH writes out every hole, so the backup of a virtual-machine image is the full declared size and so is the restore. And the number in your backup report is probably the apparent size: restic called a 40K directory processed 5 files, 128.026 MiB.

Hard links. Four names on one inode. Distinct inodes after each round trip: tar, bsdtar, cp -a and rsync -aH give 1; rsync -a and cp -r give 4. 8.1M becoming 529M is real, but the loss is that the identity relationship is gone, and for a --link-dest snapshot tree that is the whole design.

The things that are not files. Fifos and device nodes come through tar, rsync -a (-a includes -D) and cp -a, and are skipped by anything that drops -D or runs unprivileged, with skipping non-regular file. Sockets are the exception:

tar     ./app.sock: socket ignored
bsdtar  ./app.sock: pax format cannot archive sockets
rsync -a, cp -a:  the socket file is recreated

connecting to the restored socket:
  after tar:      [Errno 2]   No such file or directory
  after rsync -a: [Errno 111] Connection refused

A restored socket file is worse than a missing one. Those errors are the two halves of The Life of a Unix Socket‘s hinge, and a stale socket path gives EADDRINUSE to bind(): the daemon that starts after your restore cannot take its own name back, on a machine where nothing is listening.

Verify: du -sh and du -sh --apparent-size on the backup, before you need the space; if they differ by an order of magnitude the restore needs the larger number unless the command carries --sparse or -S. Then find / -xdev -type s on the restored machine, and delete what you find before starting the services.

Stage 6: What the walk could not see

A tool that walks a filesystem cannot see two things: what is outside the tree, and what was moving outside the instant. Both produce restores that come up, run, and are wrong.

Outside the tree

The partition table lives in the first thirty-three sectors of the block device; anything that walks a filesystem reads through the filesystem and never goes near them. The filesystem UUID is the same problem with a sharper edge, because your machine names it in a file that is in the backup:

$ grep -a -c '730784A8-B61E-4AF5-A455-730370DEF1F5' contents.tar   # the GPT label-id
0

old filesystem UUID : e5035ff4-1bfc-4d68-8d21-717e462be937
new filesystem UUID : a6627e08-90cf-4c79-934b-d51f58c3379a     # after mkfs on the rebuild

$ grep UUID /etc/fstab                                          # restored, verbatim
UUID=e5035ff4-1bfc-4d68-8d21-717e462be937  /srv/appdata  ext4  defaults  0 2

the only backup of a partition table is one you took on purpose:
    sfdisk -d /dev/sda > /backup/sda.table       #  restore: sfdisk /dev/sda < …

The only copy of the old UUID in your backup is the line that refers to it. The thing it referred to was destroyed with the disk; the reference came back intact, which is From Power-On to a Login Prompt‘s worked diagnosis by another road. LVM metadata is the same category, with vgcfgbackup and vgcfgrestore. The LUKS header is the sharpest of the three (taking one is in Disk Encryption on a Laptop); what matters at restore time is what that file is:

format with passphrase A ; luksHeaderBackup  -> header.img (16,777,216 bytes)
rotate to passphrase B   ; A now refused, B accepted

months later, restore the header backup:
$ cryptsetup luksHeaderRestore … ; echo "exit=$?"
exit=0
  passphrase A opens it?  YES      <- the one you rotated out is live again
  passphrase B opens it?  No key available with this passphrase.

A header backup is a snapshot of the key slots on the day you took it. Restoring it un-revokes every passphrase retired since and revokes the current one, with exit 0 and no output. Still take it, since the alternative is an unrecoverable disk, and re-take it after every rotation, destroying the old one.

Outside the instant

The standard warning about databases is that a copy taken while one is running may not restore. It warns about the outcome that announces itself and says nothing about the one that does not. A complete hot copy of a running PostgreSQL data directory, rsync -aH mid-transaction, is crash-consistent:

LOG:  database system was not properly shut down; automatic recovery in progress
LOG:  redo done at 0/2A66658
LOG:  database system is ready to accept connections
  rows in the live database at the end : 20183
  rows in the restored copy            : 20105

# the same copy, with the sensible-looking exclusion --exclude='pg_wal/*'
LOG:    invalid checkpoint record
PANIC:  could not locate a valid checkpoint record
LOG:    startup process was terminated by signal 6: Aborted

The complete copy is not corrupt. It is a valid database at a moment nobody recorded; two copies of the same live database taken seconds apart recovered to two different log positions, both reporting success. The one that will not start is the incomplete one, made by an exclude list written to avoid backing up logs.

SQLite, which most self-hosted applications actually run, is the deterministic version. In WAL mode committed data lives in a sidecar file until a checkpoint moves it. Back up the main file alone, as any file-level backup with an exclude list does:

live:      5,010 committed rows   app.db 8,192 bytes   app.db-wal 127,752 bytes
restored:  PRAGMA integrity_check -> ok
           SELECT count(*)        -> 10

Five thousand committed transactions gone, and the database says it is fine. Dump databases rather than copy their files, not because the copy will fail but because it will succeed at a moment nothing wrote down. Per engine: Running PostgreSQL Properly, etcd, Honestly, and for SQLite sqlite3 app.db ".backup out.db" against the live file.

Verify: sfdisk -d each disk and blkid the machine, and put both in the backup as text. Then compare /etc/fstab‘s UUIDs against blkid before the first reboot.

Stage 7: The other copy, and everyone who cannot tell them apart

The standard migration advice, including ours, is to leave the old server running for a week as your rollback. A restore does not move a machine. It makes a second one, and while the first exists there are two things on the network claiming to be the same host, and every party who has to tell them apart is somewhere you cannot reach from either console.

The SSH host keys. Automated Backups includes /etc/ssh and Server Migration does not, and both directions are wrong differently:

restore without /etc/ssh:  every client gets
    @  WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!  @
    Host key verification failed.

restore /etc wholesale:    the old identity comes back, and so does every
    client's trust in it — including the clients of the machine still running

The first is loud and harmless: a warning that reads like a break-in is the correct response to a machine that is not the one you talked to yesterday. The second is silent. Two hosts now present the same key, and no client anywhere can distinguish them. Neither machine can see the collision, because it is in every known_hosts file in the organisation.

machine-id. The identifier almost everyone treats as cosmetic:

/etc/machine-id            c1d60f590676409fadd11ab338e333b8
/var/lib/dbus/machine-id   c1d60f590676409fadd11ab338e333b8
journal directory          /var/log/journal/c1d60f590676409fadd11ab338e333b8

# an application-specific ID, derived from it by HMAC — deterministic
app A -> 5e4ec84094d240ba9c0b1dbc7b281e2d

# the same one, after another machine's /etc/machine-id is restored over it
app A -> 5352f34ec1f448fd8500b79d89522045

Every derived identifier moved, so the reverse holds: two machines carrying the same /etc/machine-id compute the same value for every one of them, with nothing to compare against. It also names the journal directory. And machine-id(5) says an image for more than one machine should carry the file “either missing or an empty file”: a missing file means first boot, an empty one does not, so a restore that writes a populated machine-id onto a fresh install suppresses every ConditionFirstBoot=yes unit and nothing reports it. That is quoted from the manual page, not watched.

So the last step of a restore is a list of parties elsewhere now holding a stale belief about this machine: every client’s known_hosts, monitoring keyed on hostname or machine ID, the log aggregator, anything that issued a licence or token to this host, and whatever holds a lease or node identity. None is readable from the console you are sitting at.

Verify: you cannot, from here. Decide instead which identity this machine has. If it replaces the original: keep the host keys and the machine-id, and switch the original off rather than “leave it running for a week”. If it is a second machine: regenerate the host keys and truncate /etc/machine-id to empty, empty, not deleted and not a fresh value typed by hand, and let systemd populate it at boot.

A worked diagnosis: the restore that worked, and everyone who could log in

A small application server loses a disk. It is rebuilt from the same distribution, the packages are reinstalled, and the nightly rsync -aH snapshot is restored over the top as root. The site serves, diff -r against the snapshot is clean, everybody logs in. Over the following fortnight four things happen, and nobody connects them:

  • The disk fills. A media directory that was thirty-six kilobytes is now two hundred and fifty-seven megabytes, and nothing has been uploaded.
  • A nightly job that binds a low port stops working: Permission denied on a binary whose mode bits are visibly correct and which runs by hand under sudo.
  • The deploy user can no longer edit the files the web server writes. It could last month, and its group memberships have not changed.
  • A partner’s CI job starts refusing to connect. Their engineer says the host key changed.

Nobody deployed, the package versions match, ls -l on the binary is right. The snapshot is still on the backup host, so both halves exist for the hinge:

$ rsync -n -i -aHAX --checksum /mnt/backup/latest/srv/ /srv/
.f........x srv/bin/collector
.f.......ax srv/www/uploads/
.f........x srv/bin/tickd
hf          srv/media/frame-0001.png => srv/media/frame-0000.png

The letters are the diagnosis. Three x: extended attributes present in the backup and absent on the machine, stage 4, and the missing one on collector is security.capability, which is why a binary with correct mode bits cannot bind port 80. One a: the ACL entry that let the deploy user write files the web server owns, gone with the same flags. One h: hard links that came back as copies, stage 5, and the disk. One command caused all three: rsync -aH carries ownership, modes and hard links and does not carry ACLs, extended attributes or sparseness. The backup had all three; the restore declined them.

The fourth symptom is not in that output and never will be. The host key is stage 7, outside the tree: /etc/ssh was not in this backup’s source list, so there is nothing to compare. An empty branch means “nothing I can compare differs”, not “nothing differs”. The fix for three symptoms was two more letters; the fix for the fourth was to talk to somebody outside the building.

Symptoms, and which stage they belong to

SymptomStageWhat is actually true
The restore finished, diff says identical, something is still wrongalldiff compares bytes. Six of the seven stages are not bytes
ls -l shows numbers where names used to be2The number has no name here, and the next useradd -r will claim it
Everyone logs in, and nobody owns their own home directory2/etc/shadow is keyed by name and /etc/passwd by number; half a restore of those files is silently wrong
Every restored file is owned by whoever ran the restore, or is 06003Only a privileged process can give a file away, and non-root extraction subtracts the umask
The backup job has been exiting 23 for months3Some files were never transferred. Usually private keys or device nodes
A service fails with Permission denied on a binary whose mode is right4A file capability was dropped on extraction
tar --acls --xattrs --selinux and the capability is still gone4GNU tar extracts only the user namespace by default. --xattrs-include='*', on the extract
An ACL that was set for years is not there any more4rsync needs -A; cp -p keeps ACLs and cp --preserve=xattr does not
The restore needs far more disk than the machine it came from5Sparse files written out in full, or hard links restored as copies
A daemon will not start: address already in use, nothing is listening5A socket file was recreated by the restore. Delete it
Emergency shell after the first reboot, or a database missing recent rows6An fstab UUID destroyed with the disk; or a hot copy that is valid at a moment nobody recorded
Two machines are indistinguishable to everything on the network7The host keys and machine-id were restored, and the original is still running

Before you call the restore done

rsync -n -i -aHAX --checksum /backup/tree/ /restored/tree/   # 1-6: the hinge
getfattr -Rd -m 'security|system' /backup/tree/ | head       # 1: did the backup have it
find /srv /home /var/www -xdev -printf '%u %g\n' | sort | uniq -c   # 2: names or digits
getcap -r /usr /opt /srv 2>/dev/null                         # 4: against the old list
du -sh --apparent-size /backup/tree/ ; du -sh /backup/tree/  # 5: before the disk fills
find / -xdev -type s                                         # 5: sockets the restore made
blkid ; grep UUID /etc/fstab                                 # 6: before the first reboot
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub ; cat /etc/machine-id   # 7: who is this

Two decisions are yours rather than a command’s. Run the restore as root — it is the recommendation with more measured evidence behind it than anything else here. And decide which machine this is before you restore /etc: a replacement keeps the host keys and the machine-id and requires the original to be off; anything else regenerates the keys and leaves /etc/machine-id empty.

Related reading

  • Automated Backups — the page above this one. Its step 7 is the procedure this page finishes
  • Permissions and Privilege, Properly — what ACLs, the mask and file capabilities are, and the order the kernel checks its six layers in
  • Running PostgreSQL Properly — the eight stages between a COMMIT and data you could still restore a year later, including the restore itself
  • How Time Works on a Linux Machine — why a restored file’s timestamps are a receipt for the restoring machine’s clock, and why that clock may be wrong while the machine insists it is not