Most of us have been burned by this at least once. You copy a file to a USB drive, the prompt comes back, you pull the drive, and the file is either corrupt or missing entirely. The frustrating part is that everything looked fine. No errors, no warnings. Linux just lied to you — or more accurately, you misunderstood what Linux was telling you.
This article explains what is really happening under the hood, why some tools are more reliable than others, and how to make absolutely sure your data is physically on the media before you remove it.
THE WRITE PIPELINE
When you write a file in Linux, your data does not go directly to the disk. It goes through a layered pipeline:
Your application writes to the kernel page cache (RAM)
The kernel eventually flushes the page cache to the block layer
The block layer submits I/O requests to the storage driver
The drive's internal write cache receives the data
The drive commits the data to its storage media
When your prompt returns after a cp command, you are typically only guaranteed that step 1 has completed. Everything below that is happening asynchronously in the background. The kernel is perfectly happy to let you keep working — or pull the drive — while steps 2 through 5 are still in progress.
This is by design. This write-back caching is one of the main reasons Linux I/O feels fast. The tradeoff is that you have to explicitly ask for a guarantee when you need one.
SYNC VS FSYNC — NOT THE SAME THING
Most people reach for the sync command when they want to flush buffers before removing a drive. It works, sometimes. But it is important to understand what sync actually does and where it falls short.
The sync command issues a global flush request to the kernel telling it to write all dirty pages to their respective block devices. The problem is that historically, sync submitted those write requests and then returned immediately without waiting for confirmation that the writes completed. It was fire-and-forget at the kernel level.
This behavior changed in kernel 5.8, where sync was updated to actually wait for writes to complete before returning. But because behavior varied across kernel versions and distributions, sync developed a reputation for being unreliable — and that reputation is at least partially deserved depending on what kernel you are running.
The fsync() system call is a different beast entirely. When a program calls fsync() on a file descriptor, the kernel blocks until the storage device confirms that the data is physically written. Not cached in RAM, not queued in the block layer, not sitting in the drive's internal buffer — actually on the media. Only then does fsync() return.
This is the difference between "I submitted your request" and "your request is done."
CP DOES NOT FSYNC
The standard cp command writes data to the page cache and exits. It does not call fsync(). This means the moment your prompt returns, your data may exist only in RAM. The kernel will flush it eventually — sometimes in seconds, sometimes in minutes depending on dirty page writeback timers — but you have no guarantee.
You can observe this yourself. Copy a large file to a USB drive with cp, note how fast the prompt returns, and then watch iostat or check /proc/meminfo for dirty page count. The drive is still writing.
If you want cp to behave more reliably, you have a few options.
OPTION 1 — DD WITH CONV=FSYNC
dd is the tool most of us already know for writing ISO images to USB drives, and the conv=fsync option is the reason it is reliable for that purpose. After writing all the data, dd calls fsync() on the output file descriptor before exiting. The prompt does not return until the drive has physically confirmed the write.
This is not just for ISO images. You can use dd with conv=fsync to copy any single file and get a guaranteed flush on exit. It is not the most elegant tool for general file copying, but it gets the job done.
OPTION 2 — RSYNC WITH --FSYNC
If you are copying files rather than writing directly to a block device, rsync with the --fsync flag is the cleanest solution. It calls fsync() on each file after writing it, giving you the same guarantee as dd conv=fsync but with rsync's full feature set including progress reporting, checksumming, and partial transfer recovery.
This option requires rsync 3.2.3 or newer. Check your version with rsync --version before relying on it.
OPTION 3 — SYNC WITH A SPECIFIC FILESYSTEM
If you are set on using cp and just want to flush afterward, the sync command has a --file-system option that is more targeted and more reliable than a bare sync call:
The --file-system flag causes sync to call syncfs() on the filesystem containing that file, rather than issuing a global flush. On modern kernels this does wait for completion. It is more precise than a global sync and less likely to return before the writes are actually done.
This requires GNU coreutils 8.24 or newer.
OPTION 4 — EJECT INSTEAD OF UMOUNT
If you are working with removable media mounted via the mount command, use eject rather than umount when you are done. eject does several things that umount alone does not: it flushes all pending writes, unmounts the filesystem, and then sends the hardware eject command to the device. The drive will not pop out until the kernel is satisfied that all data is on the media.
or by mount point:
If the device is not physically ejectable, eject will still handle the flush and unmount cleanly. You can also use:
This is the udisks2 equivalent, commonly used in desktop environments, and it also ensures a clean flush before powering off the device.
Here is a quick reference for the tools and when each one gives you a real guarantee:
cp (bare)Writes to page cache. Prompt returns immediately. No guarantee data is on media.
sync (bare, old kernels)Submits flush requests but may return before writes complete. Unreliable.
sync (kernel 5.8+)Waits for write completion. More reliable but still a global operation.
sync --file-system /path/to/fileFlushes just the relevant filesystem and waits. Better choice than bare sync.
dd if=source of=dest bs=4M conv=fsyncCalls fsync() before exiting. Reliable guarantee.
rsync --fsync source destCalls fsync() per file. Clean, scriptable, reliable. Requires rsync 3.2.3+.
eject /dev/sdXFlush, unmount, and hardware eject in one step. Best practice for removable media.
udisksctl power-off --block-device /dev/sdXDesktop-friendly equivalent to eject with the same guarantees.
The kernel is not trying to deceive you. Write-back caching is a feature, and a good one. But when you are working with removable media or any situation where the data absolutely has to survive — use a tool that calls fsync(), or explicitly eject the device through the proper channel. A prompt returning to you is not a promise. An fsync() is.
This article explains what is really happening under the hood, why some tools are more reliable than others, and how to make absolutely sure your data is physically on the media before you remove it.
THE WRITE PIPELINE
When you write a file in Linux, your data does not go directly to the disk. It goes through a layered pipeline:
Your application writes to the kernel page cache (RAM)
The kernel eventually flushes the page cache to the block layer
The block layer submits I/O requests to the storage driver
The drive's internal write cache receives the data
The drive commits the data to its storage media
When your prompt returns after a cp command, you are typically only guaranteed that step 1 has completed. Everything below that is happening asynchronously in the background. The kernel is perfectly happy to let you keep working — or pull the drive — while steps 2 through 5 are still in progress.
This is by design. This write-back caching is one of the main reasons Linux I/O feels fast. The tradeoff is that you have to explicitly ask for a guarantee when you need one.
SYNC VS FSYNC — NOT THE SAME THING
Most people reach for the sync command when they want to flush buffers before removing a drive. It works, sometimes. But it is important to understand what sync actually does and where it falls short.
The sync command issues a global flush request to the kernel telling it to write all dirty pages to their respective block devices. The problem is that historically, sync submitted those write requests and then returned immediately without waiting for confirmation that the writes completed. It was fire-and-forget at the kernel level.
This behavior changed in kernel 5.8, where sync was updated to actually wait for writes to complete before returning. But because behavior varied across kernel versions and distributions, sync developed a reputation for being unreliable — and that reputation is at least partially deserved depending on what kernel you are running.
The fsync() system call is a different beast entirely. When a program calls fsync() on a file descriptor, the kernel blocks until the storage device confirms that the data is physically written. Not cached in RAM, not queued in the block layer, not sitting in the drive's internal buffer — actually on the media. Only then does fsync() return.
This is the difference between "I submitted your request" and "your request is done."
CP DOES NOT FSYNC
The standard cp command writes data to the page cache and exits. It does not call fsync(). This means the moment your prompt returns, your data may exist only in RAM. The kernel will flush it eventually — sometimes in seconds, sometimes in minutes depending on dirty page writeback timers — but you have no guarantee.
You can observe this yourself. Copy a large file to a USB drive with cp, note how fast the prompt returns, and then watch iostat or check /proc/meminfo for dirty page count. The drive is still writing.
If you want cp to behave more reliably, you have a few options.
OPTION 1 — DD WITH CONV=FSYNC
dd is the tool most of us already know for writing ISO images to USB drives, and the conv=fsync option is the reason it is reliable for that purpose. After writing all the data, dd calls fsync() on the output file descriptor before exiting. The prompt does not return until the drive has physically confirmed the write.
Code:
dd if=sourcefile of=/dev/sdX bs=4M conv=fsync status=progress
This is not just for ISO images. You can use dd with conv=fsync to copy any single file and get a guaranteed flush on exit. It is not the most elegant tool for general file copying, but it gets the job done.
OPTION 2 — RSYNC WITH --FSYNC
If you are copying files rather than writing directly to a block device, rsync with the --fsync flag is the cleanest solution. It calls fsync() on each file after writing it, giving you the same guarantee as dd conv=fsync but with rsync's full feature set including progress reporting, checksumming, and partial transfer recovery.
Code:
rsync --fsync sourcefile /path/to/destination/
This option requires rsync 3.2.3 or newer. Check your version with rsync --version before relying on it.
OPTION 3 — SYNC WITH A SPECIFIC FILESYSTEM
If you are set on using cp and just want to flush afterward, the sync command has a --file-system option that is more targeted and more reliable than a bare sync call:
Code:
cp bigfile /mnt/usb/ && sync --file-system /mnt/usb/bigfile
The --file-system flag causes sync to call syncfs() on the filesystem containing that file, rather than issuing a global flush. On modern kernels this does wait for completion. It is more precise than a global sync and less likely to return before the writes are actually done.
This requires GNU coreutils 8.24 or newer.
OPTION 4 — EJECT INSTEAD OF UMOUNT
If you are working with removable media mounted via the mount command, use eject rather than umount when you are done. eject does several things that umount alone does not: it flushes all pending writes, unmounts the filesystem, and then sends the hardware eject command to the device. The drive will not pop out until the kernel is satisfied that all data is on the media.
Code:
eject /dev/sdX
Code:
eject /mnt/usb
Code:
udisksctl power-off --block-device /dev/sdX
This is the udisks2 equivalent, commonly used in desktop environments, and it also ensures a clean flush before powering off the device.
Here is a quick reference for the tools and when each one gives you a real guarantee:
cp (bare)Writes to page cache. Prompt returns immediately. No guarantee data is on media.
sync (bare, old kernels)Submits flush requests but may return before writes complete. Unreliable.
sync (kernel 5.8+)Waits for write completion. More reliable but still a global operation.
sync --file-system /path/to/fileFlushes just the relevant filesystem and waits. Better choice than bare sync.
dd if=source of=dest bs=4M conv=fsyncCalls fsync() before exiting. Reliable guarantee.
rsync --fsync source destCalls fsync() per file. Clean, scriptable, reliable. Requires rsync 3.2.3+.
eject /dev/sdXFlush, unmount, and hardware eject in one step. Best practice for removable media.
udisksctl power-off --block-device /dev/sdXDesktop-friendly equivalent to eject with the same guarantees.
The kernel is not trying to deceive you. Write-back caching is a feature, and a good one. But when you are working with removable media or any situation where the data absolutely has to survive — use a tool that calls fsync(), or explicitly eject the device through the proper channel. A prompt returning to you is not a promise. An fsync() is.

