VPS Snaps

How to make a full disk image of a server with dd

Boot the server into a rescue system so its disk is not mounted, then stream the whole device through a compressor to another machine: dd if=/dev/vda bs=4M status=progress | zstd -T0 | ssh [email protected] 'cat > /srv/images/web-01-vda.img.zst'. To restore, run the pipe the other way into dd of=/dev/vda. An image is an exact copy of every block, which makes it the right tool before a risky change and the wrong one for nightly backups.

9 min readUpdated Tested on Ubuntu 24.04 with GNU coreutils 9.4, zstd 1.5.5 and util-linux 2.39.3, on disk image files

When an image is the right tool, and when it is not

dd copies a device block by block: partition table, bootloader, every filesystem and the free space. Use it when you need a disk back exactly as it was:

  • Before a risky change to the disk: repartitioning, converting a filesystem, an in-place upgrade with no snapshot feature.
  • Moving a whole server to a host that lets you write a raw disk: a rescue system elsewhere, a Proxmox or KVM host, bare metal.
  • Keeping an exact copy of a disk you are about to wipe.

For routine backups it is the wrong tool: every image is the size of the disk, one file back means unpacking and mounting it, and the server is offline while it runs. Use file tools such as restic or tar plus database dumps. On a cloud server, the provider's snapshot does an image's job without downtime (snapshots vs backups).

Never image a mounted disk

Files change while dd reads a running system, so early blocks no longer match later ones and the image's filesystem can come out damaged, not just out of date. Instead:

  • Boot a rescue system: most providers offer a rescue or recovery boot with your disk attached but not mounted. Without one, stop the server and attach its disk to another.
  • Or image a frozen LVM snapshot device. That covers one volume, not the partition table and bootloader. On ZFS, use zfs send instead of dd.

Find the disk

Terminal
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS

Image the whole disk (TYPE disk: vda, sda, nvme0n1), not a partition like vda1, so the partition table and bootloader come along. In the rescue system, your server's disk is the one without mount points; names can differ from a normal boot. Older lsblk: MOUNTPOINT. For the restore, note the size in bytes (-b) of the disk alone (-d):

Terminal
lsblk -b -d -o NAME,SIZE /dev/vda

Zero the free space first

Free space still holds what deleted files left, and a compressor can't shrink that. On the running server, just before the rescue boot, fill the free space with zeros, then delete the file (once per filesystem on the disk):

Terminal
sudo dd if=/dev/zero of=/zero.fill bs=4M status=progress conv=fsync
Terminal
sudo rm /zero.fill

dd ends with dd: error writing '/zero.fill': No space left on device and exit status 1: the expected end, so don't let set -e stop a script there. conv=fsync flushes the zeros before dd exits, even after the error; otherwise deleting the file can discard zeros still in memory.

For a moment the filesystem is full, root's reserved blocks included, and any write fails, a database's too. Stop busy services first. On SSD-backed or thin-provisioned disks the zeros take real space and write the whole free area.

Worked example, on a 64 MiB test image with 16 MiB of incompressible data: with 45 MiB of leftover random data in the free space, zstd -T0 made 63,965,024 bytes (61 MiB); with it zeroed, 16,779,500 bytes (16 MiB). gzip -1: 63,992,445 and 17,002,698. Real leftovers compress better than random bytes, never as well as zeros.

After the image is taken, sudo fstrim -v / lets SSD or thin-provisioned storage release the unused blocks. Not before: what a trimmed block reads back as depends on the device.

Make the image and send it to another machine

In the rescue system, as root:

Terminal (rescue system)
dd if=/dev/vda bs=4M status=progress | zstd -T0 | ssh [email protected] 'cat > /srv/images/web-01-vda.img.zst'
PartWhat it does
if=/dev/vdaThe whole disk. With no of=, dd writes to the pipe.
bs=4MRead 4 MiB at a time; the 512-byte default is much slower. GNU dd wants a capital M (or 4MiB).
status=progressPrint progress to standard error, at most once a second.
zstd -T0Compress on all physical cores at level 3, with a checksum; both are zstd's defaults.
ssh … 'cat > …'Write the stream to a file on the backup host, never to the disk being imaged.

A healthy disk needs no conv= options. Without zstd, gzip -1 makes a .img.gz the same way. If the rescue system has no key for the backup host, pull from the backup host instead: ssh [email protected] 'dd if=/dev/vda bs=4M | zstd -T0' > web-01-vda.img.zst.

Prove the copy: sha256sum /dev/vda in the rescue system must match this on the backup host. Each reads the whole disk, so allow as long as the image took.

Terminal (backup host)
zstd -dc /srv/images/web-01-vda.img.zst | sha256sum

dd never asks. if= is the source, of= the target: swap them, or type vdb for vda, and a disk is overwritten. Run lsblk right before any dd that writes to a device, and read the command twice.

Check the image without restoring it

On the machine holding it, test the file. zstd prints the uncompressed size, which should equal the disk's:

Terminal
zstd -t /srv/images/web-01-vda.img.zst

Unpack with --sparse so runs of zeros become holes. Our 64 MiB test image took 16 MiB of disk with it, 64 MiB without:

Terminal
zstd -d --sparse /srv/images/web-01-vda.img.zst
Terminal
fdisk -l /srv/images/web-01-vda.img

fdisk lists the image's partitions and start sectors as for a disk. To read files, attach it as a read-only loop device:

Terminal
sudo losetup -P -r -f --show /srv/images/web-01-vda.img

-f --show takes the first free loop device and prints it (say /dev/loop0); -P scans the partition table, giving /dev/loop0p1 and so on; -r makes it read-only.

Terminal
sudo mount -o ro /dev/loop0p1 /mnt/image

An image of a disk that wasn't cleanly unmounted needs a journal replay, which a read-only device can't take; -o ro,noload (ext4) or -o ro,norecovery (XFS) skips it, and the docs warn the result may be inconsistent. Then sudo umount /mnt/image and sudo losetup -d /dev/loop0.

Restore the image

Boot the target into its rescue system. Its disk must be at least the size lsblk -b showed. As root:

Terminal (rescue system)
ssh [email protected] 'cat /srv/images/web-01-vda.img.zst' | zstd -dc | dd of=/dev/vda bs=4M iflag=fullblock conv=fsync status=progress
  • iflag=fullblock reads until each 4 MiB block is full; a pipe delivers smaller pieces. The data is the same without it, in smaller writes.
  • conv=fsync flushes to disk before dd exits: finished means written.
  • No conv=sparse onto a disk: it skips zero blocks, leaving old data in them.

Partition table, bootloader and UUIDs come back as they were, so /etc/fstab and the bootloader still find their filesystems. So do the hostname, SSH host keys and static network settings, which may not fit a new host; fix those from the provider's console. The target must boot the same way (BIOS or UEFI). A copy attached next to its original duplicates their UUIDs: give an unmounted ext4 copy a new one with tune2fs -U random.

On a larger disk, GPT's backup header is no longer at the end. For our 64 MiB image on a 96 MiB disk, fdisk -l reported GPT PMBR size mismatch (131071 != 196607) will be corrected by write. and The backup GPT table is not on the end of the device. Still in the rescue system, move the header, then grow the partition that ends last (1 here; check fdisk -l):

Terminal (rescue system)
sfdisk --relocate gpt-bak-std /dev/vda
Terminal (rescue system)
echo ', +' | sfdisk -N 1 /dev/vda

After booting, grow the mounted filesystem: sudo resize2fs /dev/vda1 (ext4) or sudo xfs_growfs / (XFS).

Alternatives to dd

partclone copies only the blocks a filesystem uses, so the image is about the size of the data and needs no zeroing. It works per partition: save the table with sfdisk -d /dev/vda > vda.sfdisk (restore: sfdisk /dev/vda < vda.sfdisk).

Terminal (rescue system)
partclone.ext4 -c -s /dev/vda1 -o - | zstd -T0 | ssh [email protected] 'cat > /srv/images/web-01-vda1.pcl.zst'

-c clones, -s names the source, -o - writes to standard output. Restore into a partition at least as large by piping the image into partclone.ext4 -r -s - -o /dev/vda1. Other filesystems have their own (partclone.xfs, partclone.btrfs).

Provider snapshots image a cloud server without a rescue boot: DigitalOcean, Hetzner, Vultr, Linode, AWS EC2, Lightsail, Google Cloud and Azure. File backups restore onto any new server, whatever its disk size (restoring a server from backup).

dd's output and errors explained

Every dd run ends with three lines. This is our 64 MiB test image restored through a pipe without iflag=fullblock:

Output
0+1037 records in
0+1037 records out
67108864 bytes (67 MB, 64 MiB) copied, 1.76665 s, 38.0 MB/s

W+P is W whole blocks plus P partial ones, reads or writes that moved less than bs. Every pipe read here was partial, yet the checksum matched; iflag=fullblock gave 16+0.

Never use conv=sync on a pipe without iflag=fullblock: sync pads every short read with zeros up to bs. The same restore with conv=noerror,sync printed 0+1025 records in and 1025+0 records out, writing 4,299,161,600 bytes (4.0 GiB) from a 64 MiB image.

MessageCauseFix
dd: error writing '/zero.fill': No space left on deviceZero-fill reached the end of the free space.Expected. Delete the file.
dd: error writing '/dev/vda': No space left on deviceRestoring onto a smaller disk.Use a disk at least as large, or restore from file backups.
dd: error reading '/dev/vda': Input/output errorAn unreadable sector; dd stops there.Failing disk: use GNU ddrescue, or conv=noerror,sync iflag=fullblock with a small bs, so a bad read zeroes one small block.
dd: failed to open '/dev/vda': Permission deniedNot running as root.sudo, or log in as root in the rescue system.
dd: invalid number: '4m'Lowercase m.GNU dd: bs=4M (lowercase k works, m doesn't).
GPT PMBR size mismatch (… != …) will be corrected by write.A GPT image on a larger disk.sfdisk --relocate gpt-bak-std /dev/vda.

Frequently asked questions

Can I use dd on a running server?
Not on its mounted disk: files change while dd reads, and the image can hold a damaged filesystem. Boot a rescue system, or image an LVM snapshot (on ZFS, use zfs send).
How big is a dd image?
Uncompressed, exactly the disk's size. Compressed after zeroing free space, close to the compressed size of the data actually stored.
What do "records in" and "records out" mean in dd?
Blocks read and written, as whole+partial. 16+0 is 16 full blocks; 0+1037 is 1037 short reads, normal when reading from a pipe.
Can I restore a dd image to a smaller disk?
No. dd writes every block and stops with "No space left on device". Restore from file backups instead, or shrink the filesystem and partition before taking the image.

How this was checked

The commands were run on Ubuntu 24.04 with GNU coreutils 9.4, zstd 1.5.5 and util-linux 2.39.3, on disk image files on October 4, 2026. Any that need something this test server does not have, such as a second server, a cloud account or another database engine, were checked against the official pages below instead.

Sources, on October 4, 2026: