Short answer: to copy a file in Linux, run cp source destination: cp report.pdf /srv/backup/ copies it into a directory, cp app.conf app.conf.bak makes a copy under a new name. To copy a folder, add -r (cp -r site /srv/backup/), or -a to also keep owners, permissions, timestamps and symlinks.
cp app.conf app.conf.bak # copy a file under a new name
cp report.pdf /srv/backup/ # copy a file to another directory
cp -r site /srv/backup/ # copy a folder and everything in it
cp -a site /srv/backup/ # the same, keeping owner, mode, times and links
cp -a site/. /srv/www/site/ # copy the contents of site into an existing folderThe outputs on this page were produced on Debian 12 with GNU coreutils 9.1, bash 5.2.15, rsync 3.2.7, pv 1.6.20 and GNU tar 1.34, as root in a scratch directory on 21 October 2026, and the messages are the ones that cp printed. The Permission denied case and the root alias on Rocky Linux and AlmaLinux are written from the coreutils documentation and those distributions’ defaults and were not run here. Ubuntu, Rocky Linux and AlmaLinux ship the same GNU cp, in other versions.
cp cheat sheet
| Task | Command |
|---|---|
| Copy a file under a new name | cp a.conf b.conf |
| Copy a file to another directory | cp a.conf /etc/myapp/ |
| Copy and rename in one step | cp a.conf /etc/myapp/main.conf |
| Copy several files into a directory | cp a.txt b.txt docs/ |
| Target directory first (for find and xargs) | cp -t docs/ a.txt b.txt |
| Copy a directory | cp -r site backup/ |
| Copy a directory, keep everything | cp -a site backup/ |
| Copy only the contents, hidden files included | cp -a site/. backup/ |
| Ask before overwriting | cp -i a.conf b.conf |
| Never overwrite | cp -n a.conf b.conf |
| Only if the source is newer | cp -u a.conf b.conf |
| Keep a backup of what gets overwritten | cp -b a.conf b.conf |
| Show each copy | cp -v a.conf b.conf |
| Recreate the path below a directory | cp --parents etc/nginx/nginx.conf backup/ |
| Follow symlinks and copy what they point to | cp -rL current backup/ |
How do I copy a file to another directory?
Name the file, then the directory. A trailing slash on the directory is optional but worth the habit: if the directory does not exist, cp fails instead of quietly creating a file with that name. -v prints each copy:
$ cp -v report.pdf backup/
'report.pdf' -> 'backup/report.pdf'
$ cp -v a.txt b.txt backup/
'a.txt' -> 'backup/a.txt'
'b.txt' -> 'backup/b.txt'
$ cp -v -t backup/ a.txt report.pdf
'a.txt' -> 'backup/a.txt'
'report.pdf' -> 'backup/report.pdf'With more than one source, the last name must be an existing directory. -t DIR puts the directory first, which is what you need behind find ... -exec cp -t DIR {} + or xargs, where the file names come last. To copy and rename at the same time, give the full new path: cp nginx.conf /etc/nginx/nginx.conf. An existing file at that path is overwritten without a question unless you add -i or -n.
How do I copy a folder in Linux?
Plain cp refuses a directory and skips it:
$ cp site site2
cp: -r not specified; omitting directory 'site'Add -r (or -R, the same thing) to copy it recursively, or -a when the copy should keep owners and timestamps (next section). Where the copy ends up depends on whether the destination already exists, and running the same command twice gives two different results:
| Command | Destination before | Result |
|---|---|---|
cp -r site site2 | site2 does not exist | site2 is the copy: site2/index.html |
cp -r site site2 | site2 exists (second run) | Copied inside it: site2/site/index.html |
cp -r site/ site3 | site3 does not exist | Same as without the slash: cp ignores a trailing slash on the source |
cp -r site/. site4/ | site4 exists | The contents, .env included, land in site4 |
cp -r site/* site5/ | site5 exists | The contents without hidden files: .env was missing |
cp -rT site site6 | site6 exists | The contents of site go into site6, never site6/site |
The site/. form is the one to remember. . is the directory itself, so cp copies everything in it, hidden files included, into the target and merges with what is already there. Same-named files in the target are overwritten silently: in the test, cp -a m_src/. m_dst/ replaced index.html and left the extra keep.txt alone. site/* looks similar, but the shell expands the * before cp runs and skips names that start with a dot.
cp -a vs cp -r: owners, timestamps and links
-r copies the data and the tree. Everything else is new: the copy belongs to whoever runs cp, and every file gets the current time. -a (archive) is -dR --preserve=all in the man page, and the difference shows up as soon as you list the copies. Source: app.conf owned by www-data, mode 640, dated 1 January 2025, with a hard link hard.conf and a symlink current.conf:
| Attribute | cp -r | cp -rp | cp -a |
|---|---|---|---|
| Owner www-data | No, became root | Yes | Yes |
| Mode 640 | Yes (see umask below) | Yes | Yes |
| Date 1 January 2025 | No, today | Yes | Yes |
| Hard link between two names | No, two separate files | No | Yes |
| Symlink inside the tree | Copied as a symlink | Copied as a symlink | Copied as a symlink |
| Extended attributes, SELinux context | No | No | Yes, where the filesystem supports them |
Mode is the subtle one. Without -p or -a, a new file gets the source’s mode minus the umask: with the usual umask 0022, a file with mode 777 came out as 755, while cp -p kept 777. Preserving the owner needs root; as a normal user, -a still copies, but the files belong to you. Use -a for backups, website moves and anything a service reads with a specific user; use -r when you want a fresh copy that belongs to you. To pick single attributes, use --preserve=mode,timestamps or turn one off with --no-preserve=ownership.
Overwriting: -i, -n, -u, -v and -f
$ cp -i new.conf old.conf
cp: overwrite 'old.conf'? n
$ cp -n new.conf old.conf; echo $?
0
$ cp -uv new.conf old.conf # new.conf is the older one: nothing happens
$ touch new.conf; cp -uv new.conf old.conf
'new.conf' -> 'old.conf'-iasks before overwriting; anything butyskips the file.-nnever overwrites. On coreutils 9.1 it skips silently, even with-v, and exits with 0. Per the GNU coreutils release notes, newer versions changed what-nreports and added--update=none, which 9.1 rejects withoption '--update' doesn't allow an argument. In scripts that run on several systems, test for the target with[ -e target ]instead.-ucopies only when the source is newer than the target or the target is missing, a cheap one-way sync for a few files.-fremoves a target that cannot be opened for writing and tries again. It does not switch off-i:cp -i -fandcp -f -iboth asked in the test. That differs from mv, where the last option wins.
That last point matters on Rocky Linux and AlmaLinux, which set alias cp='cp -i' in root’s ~/.bashrc (Debian ships the same line commented out in /usr/share/base-files/dot.bashrc). Typing cp -f there still prompts. Skip the alias with \cp or command cp: in an interactive bash with that alias, \cp z1 z2 overwrote without asking. Scripts never see the alias.
Keep the old file: --backup
$ cp -bv new.conf old.conf
'new.conf' -> 'old.conf' (backup: 'old.conf~')
$ cp -v --backup=numbered new.conf old.conf
'new.conf' -> 'old.conf' (backup: 'old.conf.~1~')
$ cp -bv -S .bak y.conf x.conf
'y.conf' -> 'x.conf' (backup: 'x.conf.bak')-b renames the file being overwritten instead of losing it. One surprise: the default method, existing, makes numbered backups as soon as one numbered backup exists. After two numbered runs had left old.conf.~1~ and old.conf.~2~, -b -S .bak wrote old.conf.~3~ and ignored the suffix. Set the method explicitly with --backup=simple or --backup=numbered when it matters.
Symlinks: -P, -L, -H
| Command | What was copied |
|---|---|
cp link.conf c1 | The file the link points to, as a regular file |
cp -P link.conf c2 | The link itself |
cp -r current d1 | The link itself (-r does not follow links) |
cp -a current d3 | The link itself |
cp -rL current d2 | The directory it points to, with its files |
cp -rH current d4 | The directory, because current was named on the command line |
cp -r current/ d5 | The directory: with the trailing slash, the path resolves through the link |
current was a link to releases/v2, the layout many deploy scripts use. Without -L or the trailing slash, a backup made with cp -r current holds a link and no files, and the link points at a relative path that may not exist where the copy lands. A link whose target is missing makes plain cp fail with cp: cannot stat 'link.conf': No such file or directory, although ls shows the name. The ln guide covers how links resolve.
Copy into folders that do not exist yet
cp does not create missing directories. cp a nodir3/x stopped with cannot create regular file 'nodir3/x': No such file or directory. Create them first with mkdir -p, or use one of two shortcuts:
$ cp -v --parents etc/nginx/nginx.conf etc/nginx/sites-available/default cfgbak/
etc -> cfgbak/etc
etc/nginx -> cfgbak/etc/nginx
'etc/nginx/nginx.conf' -> 'cfgbak/etc/nginx/nginx.conf'
etc/nginx/sites-available -> cfgbak/etc/nginx/sites-available
'etc/nginx/sites-available/default' -> 'cfgbak/etc/nginx/sites-available/default'
$ install -D -m 644 a.txt deep/new/dir/a.txt--parents recreates the source path below the target directory, a tidy way to save a handful of configs with their paths. install -D creates every missing directory of the destination and sets the mode in the same step.
Sparse files and disk images
A VM disk image or a database file can be mostly holes: large on paper, small on disk. cp detects that by default (--sparse=auto). A 20 MiB test file with three bytes of data used 4 KiB on disk, and so did its copy; --sparse=never wrote all 20 MiB. cmp found the contents identical in both cases. du shows the real usage, du --apparent-size the nominal size.
Does cp show progress?
No. GNU cp has no progress option, and -v only prints the name of each file, not how far the copy has got. For a long copy, use a tool that measures:
rsync -ah --progress disk.img /mnt/backup/ # per file
rsync -ah --info=progress2 /srv/data/ /mnt/data/ # one line for the whole job
pv disk.img > /mnt/backup/disk.img # one file through pv
tar -C /srv/data -cf - . | pv | tar -C /mnt/data -xf - # a directory through pvOn the 20 MiB test file, rsync ended with 20.97M 100% ... (xfr#1, to-chk=0/1) and pv drew a bar ending in 100%. Both are separate packages (apt install rsync pv on Debian and Ubuntu). rsync has the bonus that a second run only copies what changed, so an interrupted copy continues instead of starting over.
cp errors and what they mean
| Message | Meaning | Fix |
|---|---|---|
-r not specified; omitting directory | A source is a directory | cp -r |
are the same file | Source and target are one file: the same path, ./path, or two hard links | Check the paths; nothing was copied |
cannot stat | The source does not exist, or is a symlink whose target is missing | Check with ls -l |
target 'x': No such file or directory | Several sources, and the last name is missing | mkdir -p it first |
target 'x': Not a directory | Several sources, and the last name is a file | Give a directory as the last name |
cannot create regular file ...: Not a directory | Target written as dir/, but there is no such directory | mkdir -p, or drop the slash to copy under that name |
cannot copy a directory, ..., into itself | You copied a directory into its own subdirectory, cp -r src src/sub | Copy to a path outside the source |
cannot overwrite non-directory ... with directory | The target name exists as a file | Rename or remove the file first |
Permission denied | No read permission on the source, or no write permission on the target directory | See below |
No space left on device | The target filesystem is full | No space left on device |
The “into itself” case leaves something behind: cp reported the error and exited with 1, yet src/sub existed afterwards with one level of copied files in it. Remove it before you try again, or it ends up inside the next copy.
cp: cannot create regular file: Permission denied
cp needs read permission on every source file, plus write and execute permission on the directory it writes into. Copying into /etc, /var/www or another user’s home as a normal user fails for the second reason. Either copy with sudo cp, or fix the directory with the chown and chmod guides, so that the service user owns what it needs and nothing more. Files copied with sudo cp belong to root unless you add -a or -p, which is a common reason a web app cannot read its own config after a restore.
cp vs rsync vs scp
| cp | rsync | scp | |
|---|---|---|---|
| Built for | Copies on the same machine | Syncing trees, locally or over SSH | One-off copies over SSH |
| To another server | No | Yes | Yes |
| Second run | Copies everything again | Copies only what changed | Copies everything again |
| Interrupted copy | Start over | Rerun continues (-P keeps partial files) | Start over |
| Progress | None | --progress, --info=progress2 | Progress meter by default |
| Keep owner, mode, times | -a | -a | -p (times and modes only) |
| Mirror with deletes | No | --delete | No |
| Installed by default | Always (coreutils) | Often not on minimal images | With the OpenSSH client |
For a few files on one machine, cp is the simplest tool. For large or repeated copies use rsync, between servers rsync or scp, and when the original should disappear afterwards, mv, which renames instantly on the same filesystem instead of copying.
Copying on a server: a few habits
- Back up a config before you edit it:
sudo cp -a /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.-akeeps owner and mode, so the backup can be put back withcp -aas it was. - Do not leave backups where they are loaded. A service that includes a whole folder, such as an nginx
include sites-enabled/*;, reads adefault.bakin that folder as one more config. Keep copies in a separate folder. - Copy a website with -a, not -r, so files keep the owner PHP or nginx expects.
- A copy is not a backup of the server. On a VMHeaven VPS, a panel snapshot before a larger change covers the whole disk, and can be taken by hand or on a daily, weekly or monthly schedule.
Frequently asked
How do I copy a file to another directory in Linux?
Run cp file /path/to/dir/, for example cp report.pdf /srv/backup/. Several files go in one command: cp a.txt b.txt /srv/backup/. To give the copy a new name at the same time, write the full new path: cp app.conf /etc/myapp/main.conf.
How do I copy a folder in Linux?
Run cp -r folder destination/, or cp -a to also keep owners, permissions, timestamps and symlinks. If the destination folder already exists, cp puts the folder inside it; to copy only its contents, hidden files included, use cp -a folder/. destination/.
What is the difference between cp -r and cp -a?
cp -r copies the files and the directory tree, but the copies belong to the user running cp and get the current time. cp -a (archive) also keeps owner, group, permissions, timestamps, hard links and symlinks, which is what backups and website moves need. Keeping another user as owner requires root.
Why does cp say omitting directory?
Because one of the sources is a directory and you did not pass -r. GNU cp prints cp: -r not specified; omitting directory 'name' and copies only the files. Add -r, or -a to keep owners and timestamps as well.