Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Advanced tar: backups, compression and selective restore

By updated

Improve this page

Everybody knows how to create a .tar.gz. The RHCSA and LFCS level demands more. Incremental backups after midnight, excluding /proc and caches, sending straight to a backup server over SSH without an intermediate file, restoring a single file from a 50 GB archive: these are the moves this guide covers.

This topic is a direct RHCSA objective: "Archive, compress, unpack, and uncompress files using tar, gzip, and bzip2". The current LFCS curriculum does not name tar, but backup and restore come back in its maintenance tasks.

What you will learn

  • Create a full backup then incremental backups with --listed-incremental
  • Exclude directories or patterns with --exclude and --exclude-from
  • Choose between gzip, bzip2, xz and zstd for the speed/ratio trade-off
  • Extract a precise file from a large archive without decompressing everything to disk
  • Send an archive over SSH in a pipeline without a temporary file
  • Check the integrity of an archive before restoring

In which context?

The table below links a real operations situation to the tar option that solves it. Read it from left to right: the right column gives the keyword to remember, not the full command. Each technique is detailed further down with its check command, because a backup you have never read back is not a backup.

Situationtar technique
Daily backup of a config directory--listed-incremental
Back up /home without caches and .git--exclude
Archive a large directory on a server short of spacexz or zstd
Restore a single /etc/nginx/nginx.conf file from an archiveselective extraction
Send a backup to a remote server without mounting NFStar + ssh pipeline
Check that a backup archive is readabletar -tvf or --compare

Quick reminder: the fundamental options

A single syntax rule avoids most beginner mistakes: the -f option must be the last letter of the group, because it consumes the next argument as the archive name. Typing tar -fcz archive.tar.gz makes it read cz as a file name, and tar answers You must specify one of the '-Acdtrux' … options. The other letters fall into two families: the action mode (-c, -x, -t), only one of which is allowed per command, and the compression codec (-z, -j, -J, --zstd).

Terminal window
# Create a gzip-compressed archive
tar -czf archive.tar.gz /directory/
# List the content
tar -tvf archive.tar.gz
# Extract
tar -xzf archive.tar.gz -C /destination/

Option cheat sheet: -c create, -x extract, -t list, -v verbose mode, -f archive file, then the codecs -z gzip, -j bzip2, -J xz and --zstd zstd.


Incremental backups with --listed-incremental

A full backup archives everything. An incremental backup only archives the files modified or created since the last backup. It is the building block of a backup plan, not the plan itself: what tar will never do for you is covered further down, in the section that sets its limits.

tar handles this natively through a snapshot file (.snar) that records the state of the directory at backup time.

Step 1, full backup (level 0)

The first call has two simultaneous effects: it produces the full archive and it creates the snapshot file. Put this .snar outside the backed-up directory, otherwise it ends up in its own archive and changes at every run. The /var/backup/ directory must exist beforehand: tar does not create the snapshot's directory tree and stops on Cannot open.

Terminal window
tar --listed-incremental=/var/backup/home.snar \
-czf /var/backup/home-full-$(date +%F).tar.gz \
/home/bob/
  • --listed-incremental=/var/backup/home.snar: creates or updates the snapshot file
  • On the first call, the .snar does not exist → tar archives everything

Step 2, incremental backup (level 1+)

Terminal window
tar --listed-incremental=/var/backup/home.snar \
-czf /var/backup/home-incr-$(date +%F-%H%M).tar.gz \
/home/bob/

The second time, tar reads the snapshot and only includes the files modified since the last run.

Check

Terminal window
# Count the files in each backup
tar -tvf /var/backup/home-full-2026-04-09.tar.gz | wc -l
tar -tvf /var/backup/home-incr-2026-04-09-0200.tar.gz | wc -l

The incremental backup must contain far fewer files than the full backup. Do not be surprised to find every directory of the tree in it, even those that did not change: tar writes them again at each level so it can replay the deletions at restore time. Count the lines starting with -rw, not those starting with drwx.

Restoring an incremental chain

Restoring happens in order: full backup, then each incremental in chronological order.

Terminal window
# 1. Extract the full backup
tar --listed-incremental=/dev/null \
-xzf /var/backup/home-full-2026-04-09.tar.gz \
-C /restore/
# 2. Apply the next day's incremental
tar --listed-incremental=/dev/null \
-xzf /var/backup/home-incr-2026-04-10-0200.tar.gz \
-C /restore/

--listed-incremental=/dev/null at extraction tells tar to apply the deletions recorded in each incremental archive (files removed between two backups). Measured with GNU tar 1.35: a file deleted between the full and the incremental backup disappears from the restore with the option, and comes back without it.


Exclude files and directories

Backing up /home including browser caches, .git and node_modules directories is a waste. tar offers two exclusion mechanisms.

--exclude, exclude a pattern

A misplaced --exclude is useless: the option must appear before the path to archive. Placed after, GNU tar 1.35 warns (--exclude '.cache' has no effect), archives the files to exclude anyway and exits with code 2. Also remember to quote each pattern: under zsh, an unquoted pattern with no match stops the command (no matches found), and under bash it would be expanded if a file of the current directory matched it.

Terminal window
tar -czf /var/backup/home-clean.tar.gz \
--exclude='.cache' \
--exclude='.local/share/Trash' \
--exclude='**/node_modules' \
--exclude='**/.git' \
/home/bob/
  • A pattern without /, like .cache, excludes that name at any depth
  • You can combine several --exclude
  • ** (globbing) works with GNU tar

--exclude-from, exclusion list file

Beyond three or four patterns, the command line becomes unreadable and above all impossible to version. An exclusion file can be reviewed and shared between several backup scripts. The format is simple: one pattern per line, without quotes (tar reads the file, not the shell). Comments are not recognised: a line starting with # is an ordinary pattern, harmless as long as no file carries that name (measured). Better document the list elsewhere, in the script that uses it.

Content of /etc/backup/exclusions.txt:

.cache
.local/share/Trash
**/node_modules
**/.git
**/tmp
*.log
*.pid
/proc
/sys
/dev
/run
Terminal window
tar -czf /var/backup/system.tar.gz \
--exclude-from=/etc/backup/exclusions.txt \
/

Choose between gzip, bzip2, xz and zstd

Choosing a codec is a trade-off between the CPU time spent compressing and the disk space saved. Two columns of the table really matter: the speed, which decides whether the backup fits in its nightly window, and the ratio, which decides how much room it takes on the backup server. Compression is not built into tar: the option delegates to an external program (gzip, bzip2, xz, zstd). The codec must therefore be installed on the machine that reads back the archive, the restore machine, not only on the one that creates it. A .tar.zst backup that cannot be read on the restore server is a classic incident.

Codectar optionExtensionRatioSpeedWhen to use it
gzip-z.tar.gzMediumVery fastDefault, logs, configs, frequent transfers
bzip2-j.tar.bz2GoodSlowLong-term archiving, space first
xz-J.tar.xzExcellentVery slowDistributions, sources, minimal size
zstd--zstd.tar.zstVery goodFastModern backups, best speed/ratio balance

Concrete examples

Notice the extension used in each command: it has no technical effect (tar detects the format by itself when reading), but it documents the codec for the administrator who finds the file six months later. Note also that --zstd is a long option and does not group with the other letters: you write tar --zstd -cf, never tar -c--zstdf.

Terminal window
# gzip: everyday use (default)
tar -czf logs-$(date +%F).tar.gz /var/log/nginx/
# bzip2: long-term archiving
tar -cjf archives-2025.tar.bz2 /var/archive/2025/
# xz: source distribution
tar -cJf my-project-1.0.tar.xz ./my-project/
# zstd: fast backup with a good ratio
tar --zstd -cf backup-$(date +%F).tar.zst /home/

Check the actual compression ratio

The ratios in the table are orders of magnitude: the real gain depends entirely on your data. Text logs compress by more than 90%, JPEG images or already compressed archives gain nothing and cost CPU time for nothing. Measure on your own dataset before freezing a codec in a production script, by comparing the size of the archives with that of the source.

Terminal window
ls -lh /var/backup/home-full-2026-04-09.tar.{gz,zst,bz2,xz}
du -sh /home/bob/

What metadata does tar keep, and what does it silently lose?

By default, GNU tar keeps neither ACLs, nor extended attributes, nor SELinux contexts, and the option that turns them on must be passed on both sides, at creation and at extraction. It is the trap that turns a successful restore into an incident: the files are there, the fine-grained rights are gone, and nobody notices until the service refuses to start.

What are a file's metadata?

A file's metadata is everything the system keeps about it besides its content. You already know part of it: the owner and group (UID and GID), the rwx permissions and the dates, which ls -l and stat show. Three more, quieter ones are those tar forgets by default:

  • POSIX ACLs: permissions granted to one specific user or group, on top of the owner, group, others trio. A + at the end of the permissions in ls -l shows they exist, and getfacl file displays them.
  • Extended attributes: name=value pairs the file system attaches to the file, outside its content, for example user.origin=prod. getfattr -d file displays them.
  • SELinux context: the security label, such as httpd_sys_content_t, that SELinux compares with the process's own label before letting it read the file, on RHEL, AlmaLinux or Rocky. ls -Z shows it, and prints a plain ? on a system without SELinux, such as Ubuntu by default.

You do not need to know how to set them to follow this section: ACLs and SELinux are taught at level 3, in Refine with ACLs and SELinux. Here, only one thing matters: if your files carry them, a backup that ignores them loses them on restore.

Measurements taken on 20 September 2026 with GNU tar 1.35 on AlmaLinux 10.2, dedicated ext4 file system mounted with acl and user_xattr:

MetadataBy defaultOption neededWhat the measurement gives
Permissions and dateskeptnonenothing to do
Hard linkskeptnonesame inode after restore
POSIX ACLslost--acls on both endsu:nobody:rwx disappears
Extended attributeslost--xattrs on both endsuser.origine disappears
SELinux contextslost--selinux on both endshttpd_sys_content_t falls back to the directory default
Owner (UID/GID)depends on who extractsextraction by root1500:1600 becomes the extractor's UID
Sparse filesnot detected--sparse513 MB versus 10 KB

Why the option is passed twice

The counter-intuitive point fits in one sentence: an archive created with --acls but extracted without it still loses its ACLs. The archive does hold the information, tar simply refuses to apply it again unless asked. The measurement leaves no doubt:

Terminal window
# Archive created WITH the option, extracted WITHOUT: the ACL is lost
tar --acls -cf backup.tar folder/
tar -xf backup.tar -C /restore/
getfacl -c /restore/folder/file | grep '^user:nobody' # returns nothing
# Option on both ends: the ACL comes back
tar --acls -xf backup.tar -C /restore/
getfacl -c /restore/folder/file | grep '^user:nobody' # user:nobody:rwx

The same rule applies to --xattrs and --selinux. So write these options in your backup scripts as well as in your restore procedures, which are rarely the same files and drift apart over time.

On RHEL, a lost SELinux context breaks the service

The SELinux case deserves its own paragraph because the failure is deferred and silent. Measured on the same machine, in enforcing mode, with a file deliberately labelled httpd_sys_content_t:

Archive createdExtractionContext of the restored file
--selinuxwithout the optionvar_t (the directory default)
--selinux--selinuxhttpd_sys_content_t kept
without the option--selinuxvar_t: the information is not in the archive

The third line is the trap: passing --selinux at restore time catches up nothing if the archive did not carry it. The files are restored, the permissions are right, and the web server answers 403 because it is not allowed to read a file labelled var_t.

The fallback exists and is worth knowing: restorecon -Rv applies again the contexts expected by the policy, which is enough when the restored tree is at its usual location. It does not replace --selinux for files whose context was set by hand.

The owner depends on who extracts, not on the archive

A file owned by 1500:1600 in the archive only gets its owner back if the extraction is done by root. Extracted by a regular account, it takes the UID of whoever extracts, silently, because an unprivileged user cannot give a file away to someone else.

source : uid=1500 gid=1600
extracted by an account : uid=1001 gid=1001 <- replaced
extracted by root : uid=1500 gid=1600 <- kept

Restoring a system tree without being root therefore produces a tree whose files all belong to the operator. It is irreparable without the list of original owners, which only the archive still carries.

Sparse files, the most spectacular gain

A sparse file is a file whose empty areas consume no disk block: virtual machine images, swap files, preallocated databases. Without --sparse, tar dutifully writes every zero into the archive.

Terminal window
truncate -s 512M disk.img # 512 MB apparent, 0 byte used
tar -cf without.tar disk.img # 513 MB archive
tar --sparse -cf with.tar disk.img # 10 KB archive

511 MiB saved on a single empty file. On a server holding a few virtual machine images, forgetting --sparse makes the difference between a backup that fits on its volume and one that fills it.


Selective extraction: a single file from a large archive

No need to decompress 50 GB to disk to get back a single configuration file.

List the content to find the exact path

Selective extraction almost always fails for the same reason: the requested path does not match the one stored in the archive. So start by listing, then copy-paste the displayed path. On a large archive, this reading still decompresses the whole stream; count several minutes for several tens of gigabytes, and filter with grep rather than scrolling the output.

Terminal window
tar -tvf /var/backup/home-full-2026-04-09.tar.gz | grep nginx.conf
-rw-r--r-- bob/bob 2847 2026-04-08 22:13 home/bob/.config/nginx/nginx.conf

Extract a precise file

Terminal window
tar -xzf /var/backup/home-full-2026-04-09.tar.gz \
-C /tmp/restore/ \
home/bob/.config/nginx/nginx.conf

The path must match exactly what tar -tvf displays (without a leading / in most cases).

Extract a whole directory

The same principle applies to a directory: pass its path without the leading / and tar restores the whole subtree, permissions and dates included. The destination directory passed to -C must exist: tar does not create it and stops on Cannot open. The full path tree is recreated under this destination.

Terminal window
tar -xzf /var/backup/home-full-2026-04-09.tar.gz \
-C /tmp/restore/ \
home/bob/.config/

Extract to a different directory

Terminal window
tar -xzf /var/backup/home-full-2026-04-09.tar.gz \
--strip-components=2 \
-C /tmp/restore/ \
home/bob/.config/

--strip-components=2 removes the first 2 levels of the path in the archive (home/bob/) to extract directly into /tmp/restore/.


Transfer over SSH, without an intermediate file

One of tar's most elegant moves: create an archive on standard output, pass it straight into SSH, and extract it on the remote server, all in a single pipeline.

Send to a remote server

The - after -czf replaces the file name with standard output: tar writes nothing to the local disk, the stream goes straight through the pipe to ssh. Watch where $(date +%F) is evaluated: in double quotes, the substitution happens on the local machine before sending, which is usually the intended behaviour to date the archive by the source's clock.

Terminal window
# Archive locally, store on the remote server
tar -czf - /home/bob/projects/ | ssh bob@backup-server "cat > /backup/projects-$(date +%F).tar.gz"

Copy a tree while preserving permissions

Both tar run as root: the first to read the protected files of /etc, such as /etc/shadow, the second because only root can give the extracted files their owner back. The remote root account must therefore accept a key-based connection.

Terminal window
# Archive and extract immediately on the remote server
sudo tar -czf - /etc/ | ssh root@backup-server "tar -xzf - -C /backup/etc/"

This pipeline preserves permissions, UIDs, GIDs, symbolic links and dates, which scp -r does not always do correctly. With a regular account at the other end, every file would belong to that account, as the section on owners shows.

Fetch an archive from a remote server

The reverse direction works too: tar runs remotely and writes to its standard output, which ssh brings back and a local redirection saves. Useful when the source server lacks disk space to host the intermediate archive. Always check the received archive with tar -tvf before deleting anything at the source: a network cut produces a truncated file that looks normal until you try to read it.

Terminal window
ssh bob@source-server "tar -czf - /var/www/html/" > /backup/html-$(date +%F).tar.gz

Check the integrity of an archive

An unusable backup is worse than no backup. Always check before finding yourself in an emergency. Keep the limit of the format in mind: a tar archive carries no global checksum. A full read detects a truncation in the middle of a file, but an archive cut exactly on a block boundary can list without error. For a real guarantee, compute a sha256sum of the archive and store it on another medium.

List the content (read test)

Terminal window
tar -tvf /var/backup/home-full-2026-04-09.tar.gz > /dev/null && echo "Archive OK"

If tar returns a non-zero exit code, the archive is corrupted.

Get the right exit code in a script

The trap hides in the pipe: $? returns the code of the last element of the pipeline, so that of tail, which succeeds even when tar failed before it. A monitoring script written this way declares all its archives valid. Two fixes in bash: test tar alone, or read ${PIPESTATUS[0]} right after the pipeline.

Terminal window
# ❌ Wrong: $? holds tail's code, always 0
tar -tvf /var/backup/home-full-2026-04-09.tar.gz | tail -5
echo "Exit code: $?"
# Right: we read tar's own code
tar -tvf /var/backup/home-full-2026-04-09.tar.gz | tail -5
echo "tar exit code: ${PIPESTATUS[0]}"

A truncated archive, for example because the disk filled up during the backup, shows up as tar: Unexpected EOF in archive or as an error passed up by the decompressor, and tar exits with code 2.

Compare with the source (if it still exists)

Terminal window
tar --compare -f backup.tar.gz -C / 2>&1 | head -20

This command compares the content of the existing archive with the current file system, and prints the differences (size, date, content). The -C / option says from which root to compare the paths of the archive.


Is tar enough as a backup solution?

No, and the nuance matters: tar can produce a backup archive, it does not on its own make up a backup strategy. This guide showed you how to build a faithful archive; what follows says what the tool will never do for you, and must therefore be placed around it.

What tar does not doThe concrete consequence
No deduplicationten full backups take ten times the space, even if nothing changed
No native encryptiona stolen archive reads as is. You must chain gpg or age
No retention, no catalogueyou write the purge yourself, and must know which archive holds what
No application consistencya database archived live gives an inconsistent copy, not a backup
No handling of open filesa file modified during archiving is captured in an intermediate state
No global checksuman archive truncated on a block boundary lists without error

The most dangerous point is the fourth, because it produces no error message. tar -czf base.tar.gz /var/lib/postgresql/ always succeeds, and the archive produced is unusable: you needed a pg_dump or a service stop. A backup that succeeds is not a backup that restores.


Common troubleshooting

tar errors are terse and the real cause is rarely the one the message announces. Before looking further, tell three families apart: an archive problem (corrupted, truncated), an environment problem (missing codec, full disk) and a path problem (misplaced snapshot, leading / removed). The table below links the displayed message to the family concerned and to the fix.

SymptomLikely causeSolution
tar: Error is not recoverable: exiting nowCorrupted or truncated archiveCheck the source file, test again with -tvf
tar: Removing leading `/' from member names at creationInformation message: tar stores relative pathsNothing to fix. Extract with -C / to restore to the original location
Incremental backup archives everything despite the .snar.snar file missing or corruptedDelete the .snar and make a new full backup
A file modified right after the backup is missing from the incrementalIts change time falls in the same clock tick as the snapshot writeSpace the levels out. Measured on 20 September 2026 on ext4: modified in the same tick, the file is missed; modified 1 ms later, it is caught. Harmless for a nightly backup, but fatal in a script that chains both levels
ACLs or the SELinux context vanished after restoreThe --acls, --xattrs or --selinux option was missing at extractionReplay the extraction with the same options as at creation. restorecon -Rv catches up the standard contexts, not the ones set by hand
xz: not found then Child returned status 127xz not installed (the case of the Debian 13 image)apt install xz-utils or dnf install xz
zstd: command not found or zstd: not found, then Child returned status 127zstd not installedapt install zstd or dnf install zstd
--exclude '...' has no effect, code 2--exclude placed after the path to archivePut every exclusion before the path
Not found in archive, code 2Requested path differs from the stored oneCopy the exact path shown by tar -tvf
Extraction cut halfwayNot enough disk spacedf -h to check, free some space

Key points

  • --listed-incremental is tar's native mechanism for incremental backups: full backup on the first call, incremental afterwards.
  • At restore time, --listed-incremental=/dev/null is what replays the deletions. Without it, a file deleted between two backups comes back.
  • --exclude and --exclude-from avoid archiving caches, .git and node_modules, provided they come before the path.
  • zstd is the best choice for modern backups: a ratio comparable to bzip2, a speed close to gzip.
  • --acls and --xattrs are passed on both ends, at creation and at extraction. An archive created with the option and extracted without it still loses its fine-grained rights.
  • --sparse changes the order of magnitude on disk images: 513 MB versus 10 KB on a 512 MiB sparse file.
  • The owner is only restored if the extraction is done by root; otherwise everything belongs to the operator.
  • Selective extraction (tar -xzf archive.tar.gz path/in/archive) avoids extracting everything.
  • The tar | ssh pipeline, run as root on both ends, transfers a full tree without a temporary file and preserves permissions and owners.
  • Always test an archive with tar -tvf before you need it, and test a restore before depending on it.

Knowledge check

Check that the essentials of this guide stuck. The questions cover only what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn