Introduction
cp seems like the simplest possible operation — duplicate a file — but I’ve been burned enough times by its default behaviors (silently overwriting a destination, not preserving permissions the way I expected, not being recursive by default) that I’ve learned to be deliberate with it. This guide covers the syntax, what actually happens on disk during a copy, and the flags I reach for constantly in real work.
What Is the cp Command?
cp copies files and directories. It’s a GNU coreutils utility:
$ cp --version
cp (GNU coreutils) 9.4
Copyright (C) 2023 Free Software Foundation, Inc.
Basic Syntax
cp [OPTION]... SOURCE DEST
cp [OPTION]... SOURCE... DIRECTORY
cp [OPTION]... -t DIRECTORY SOURCE...
How cp Works Internally
Unlike mv on the same filesystem, cp always reads the actual bytes of the source and writes them to a new location — it allocates a brand-new inode for the destination and copies the data blocks. This is true even when source and destination are on the same filesystem, which is the fundamental difference from mv. I verified this directly:
$ echo "hello" > src.txt
$ cp src.txt dst.txt
$ cat dst.txt
hello
$ ls -li src.txt dst.txt
573462 -rw-r--r-- 1 root root 6 Jul 31 01:36 src.txt
573465 -rw-r--r-- 1 root root 6 Jul 31 01:36 dst.txt
Different inode numbers (573462 vs 573465) confirm dst.txt is a completely independent file with its own copy of the data — editing one has zero effect on the other, unlike a hard link.
By default, cp does not preserve every attribute of the source. Ownership typically becomes whoever ran the copy (not the original owner) unless you’re root or use -p/--preserve, and timestamps update to the copy time rather than the original modification time unless preservation is requested. This surprises people coming from mv, where ownership and timestamps carry over automatically because the inode itself is reused.
Full List of Parameters
| Option | Long form | Description |
|---|---|---|
-a | --archive | Archive mode: equivalent to -dR --preserve=all, ideal for full, faithful copies |
-b | Make a backup of each existing destination file | |
--backup[=CONTROL] | Same, with backup method control | |
-d | Same as --no-dereference --preserve=links | |
-f | --force | Remove existing destination files and retry (if copy fails) |
-i | --interactive | Prompt before overwrite |
-l | --link | Hard link files instead of copying |
-L | --dereference | Always follow symbolic links in SOURCE |
-n | --no-clobber | Do not overwrite an existing file |
-p | Same as --preserve=mode,ownership,timestamps | |
--preserve[=ATTR_LIST] | Preserve specified attributes: mode, ownership, timestamps, context, links, xattr, or all | |
-r, -R | --recursive | Copy directories recursively |
--reflink[=WHEN] | Use copy-on-write reflinks where supported (auto, always, never) | |
-s | --symbolic-link | Make symbolic links instead of copying |
-u | --update | Copy only when SOURCE is newer than destination, or destination is missing |
-v | --verbose | Explain what is being done |
-x | --one-file-system | Stay on this filesystem when copying recursively |
--help | Display help and exit | |
--version | Output version information and exit |
Practical Examples with Output
Simple copy:
$ cp src.txt dst.txt
$ cat dst.txt
hello
Copying a directory tree recursively:
$ mkdir -p a/b/c
$ cp -r a a_copy
$ find a_copy
a_copy
a_copy/b
a_copy/b/c
I confirmed this directly — the entire nested structure was reproduced under the new name.
Archive-mode copy, preserving permissions, ownership, timestamps, and symlinks — the gold standard for a faithful backup copy:
$ cp -a /etc/myapp /backup/myapp_$(date +%F)
Preventing accidental overwrite:
$ echo "important" > report.txt
$ echo "junk" > /tmp/report.txt
$ cp -n /tmp/report.txt report.txt
$ cat report.txt
important
Nothing was overwritten.
Interactive confirmation:
$ cp -i /tmp/report.txt report.txt
cp: overwrite 'report.txt'? y
Copying only newer files (useful for lightweight sync-style updates):
$ cp -u source/*.conf /etc/myapp/
Using a copy-on-write reflink on filesystems that support it (Btrfs, XFS with reflink support) — near-instant “copy” that shares data blocks until either file is modified:
$ cp --reflink=auto largefile.iso largefile_copy.iso
Common Use Cases
- Duplicating configuration files before editing them
- Copying build artifacts into a deployment directory
- Backing up directories before a risky operation
- Populating a new server with files from a golden template
- Copying files between mounted volumes or external drives
Shell Scripting and Automation
A safe “backup before edit” pattern I use constantly:
#!/usr/bin/env bash
set -euo pipefail
CONF="/etc/myapp/config.yml"
cp -a -- "$CONF" "${CONF}.bak.$(date +%Y%m%d_%H%M%S)"
Copying a whole directory tree into a deployment target while excluding certain files (using rsync since plain cp has no exclude mechanism):
rsync -a --exclude='.git' --exclude='node_modules' src/ /var/www/app/
I mention rsync deliberately here because cp -r has no built-in filtering — for anything beyond a full, unfiltered directory copy, rsync is usually the better tool even though cp can technically handle simple recursive copies.
Real-World System Administration Workflows
- Golden image provisioning:
cp -a /etc/skel/. /home/newuser/to populate a new user’s home directory with default dotfiles while preserving intended permissions. - Config staging: copying a validated config file into place with
-pto keep the same ownership expected by the consuming service. - Snapshot-style backups on Btrfs/XFS:
cp --reflink=alwaysfor near-instant, space-efficient copies that only diverge (and consume extra space) once one side is modified. - Log archiving:
cpcompleted log files to an archive directory before compression and long-term storage.
Comparing cp to Related Commands
cpvsmv:cpalways duplicates data;mvon the same filesystem just repoints a name and never duplicates data. Usecpwhen you need both copies to coexist independently,mvwhen you’re relocating or renaming.cpvsrsync:rsyncsupports incremental updates (only transferring changed portions of files), exclusion patterns, remote transfer over SSH, and resumability —cphas none of that and is best for simple local, one-shot copies.cpvsdd:ddoperates at the block level and is used for whole-device or whole-partition copies (disk images), not general file copying —cpis file-system-aware and the correct choice for ordinary files.cp -avstar-based copy (tar cf - . | (cd dest && tar xf -)): both preserve metadata well; thetarpipeline is sometimes preferred for copying across mount points with unusual permission requirements or when you want a single-pass streaming copy without an intermediate archive file.
Troubleshooting Common cp Issues
“cp: omitting directory” when copying a directory: you forgot -r/-R — cp requires an explicit recursive flag for directories, matching rm‘s protective behavior.
Copied file has wrong owner: cp by default sets ownership to the user running the copy, not the source’s owner, unless you’re root or pass -p/--preserve=ownership (as an unprivileged user, -p‘s ownership preservation only works if you already own the target UID/GID, otherwise the kernel silently keeps the copying user as owner).
Copy overwrote something unintentionally: cp overwrites the destination silently by default; add -i interactively or -n in scripts where that matters.
“cp: cannot create regular file: No space left on device” mid-copy: check df -h on the destination; also check df -i for exhausted inodes, since that failure mode gives a similar error.
Performance Considerations
Full data copies are inherently I/O-bound and proportional to file size — there’s no shortcut for a true byte-for-byte duplicate on a traditional filesystem. On copy-on-write filesystems (Btrfs, and XFS with reflink support), --reflink=auto gives you an effectively instant “copy” that only starts consuming real extra disk space once one of the two files is actually modified (a copy-on-write fork at the block level) — worth using whenever you’re duplicating large files on a filesystem that supports it. For copying many small files, cp -r can be slower than tar-based or rsync-based approaches due to per-file syscall overhead; benchmark on your actual workload if performance at scale matters.
Security Implications
Default cp behavior resets ownership to the copying user and doesn’t preserve special permission bits like setuid/setgid unless explicitly asked via -p/--preserve=mode. This is usually a good thing security-wise — copying a setuid binary without meaning to keep its setuid bit set is generally the safer default. Conversely, when you deliberately need a faithful copy of a system file including its exact permissions (for backup or migration), -a/--preserve=all is essential, and you should verify the result with ls -l afterward rather than assuming it matched.
Best Practices
- Use
-awhen you need a truly faithful copy (backups, migrations). - Use
-ior-nto avoid destructive accidental overwrites. - Use
-r/-Rexplicitly for directories —cpwon’t guess you meant to. - Prefer
rsyncovercpfor filtered, incremental, or remote copies. - Use
--reflink=autoon Btrfs/XFS for large-file duplication when speed and space matter.
Compatibility Across Distributions
GNU cp is consistent across Debian/Ubuntu, RHEL/Fedora, Arch, and openSUSE. --reflink support depends on the underlying filesystem (Btrfs and reflink-enabled XFS support it; ext4 does not), not the distribution itself — check with cp --reflink=always and watch for a fallback error if unsupported. BusyBox cp (Alpine, minimal containers) supports -r, -p, -a, and -f but has a reduced --preserve attribute list — verify with cp --help before relying on fine-grained preservation flags there.
Advanced Scenarios I’ve Run Into
Copying with sparse file awareness: some files (like disk images or database files with large gaps of zero bytes) are stored as “sparse” — the filesystem doesn’t actually allocate blocks for long runs of zeros. A naive copy can turn a sparse file into a fully-allocated one, ballooning disk usage. GNU cp since coreutils 8.x detects sparse regions automatically in most cases and preserves them, but you can be explicit with --sparse=always to force this behavior, or --sparse=never to force full allocation:
$ cp --sparse=always disk.img disk_copy.img
Copying while preserving extended attributes and ACLs, which -p alone does not cover — you need to explicitly include xattr and rely on -a (which implies --preserve=all including xattr where supported):
$ cp -a --preserve=all secured_file.txt secured_copy.txt
Copying only specific file types out of a mixed directory, combining find with cp since cp itself has no filtering logic:
$ find /var/log/myapp -name "*.log" -exec cp -t /backup/logs/ {} +
Verifying a copy actually matches the source — cp doesn’t checksum anything by default, so for anything where data integrity genuinely matters (large backups, critical config files), I follow up with an explicit comparison:
$ cp important_data.bin /backup/
$ cmp important_data.bin /backup/important_data.bin && echo "Copy verified identical"
Copy verified identical
cmp byte-for-byte compares two files and exits silently with status 0 if they’re identical, or reports the first differing byte if not — a cheap sanity check worth building into any script where a copy failure would be costly.
Copying Over SSH and Between Hosts
Plain cp only ever operates on the local filesystem (or filesystems the local kernel can already see, including network mounts like NFS). For copying between separate machines without a shared mount, the natural extensions are scp (which shares much of cp‘s flag philosophy but transfers over SSH) or rsync -e ssh, which additionally supports resuming interrupted transfers and only sending the parts of a file that actually changed on repeat runs — a meaningful advantage over a fresh cp-style full retransfer every time.
Summary
The core thing to internalize about cp is that, unlike mv, it always physically duplicates data (except on copy-on-write filesystems with --reflink), and it does not preserve ownership or timestamps by default the way a same-filesystem mv automatically does. Once that’s clear, the flags fall into place naturally: -r for directories, -a for faithful full-attribute copies, -i/-n for safety against overwrites, and --reflink=auto when you want speed on a filesystem that supports it.
References
- GNU Coreutils Manual —
cpinvocation: https://www.gnu.org/software/coreutils/manual/html_node/cp-invocation.html - Linux man-pages project —
cp(1): https://man7.org/linux/man-pages/man1/cp.1.html - Btrfs reflink / copy-on-write documentation: https://btrfs.readthedocs.io/en/latest/Reflink.html
- Ubuntu Manpage Repository: https://manpages.ubuntu.com/manpages/noble/en/man1/cp.1.html
