Linux gzip and xz Commands
last modified October 23, 2026
The gzip and xz commands compress single files.
gzip is fast and available everywhere; it compresses log
files, web content, and .tar.gz archives. xz
compresses better at the cost of more time and memory and is used for
software distribution, such as Linux kernel sources and
.tar.xz packages.
Neither command creates archives of several files; that is the job of
tar, which can call them. The examples
were run on Debian 12 with gzip 1.12, XZ Utils 5.4.1, bzip2 1.0.8, and
zstd 1.5.4. The main test file, access.log, is a 15 MB web
server log with 120,000 lines.
Compressing a file
In its basic form, gzip replaces a file with its compressed
version.
head -n 2 access.log ls -l access.log gzip access.log ls -l access.log*
The original file is gone; access.log.gz takes its place, with
a tenth of the size. Log files compress very well because they repeat the
same addresses, paths, and browser names. The compressed file keeps the
permissions and the modification time of the original. gzip
prints nothing on success.
$ head -n 2 access.log 192.0.2.176 - - [21/Oct/2026:00:00:00 +0000] "GET /about HTTP/1.1" 200 32027 "-" "Mozilla/5.0 (Macintosh) Safari/605.1.15" 192.0.2.93 - - [21/Oct/2026:00:00:00 +0000] "GET /products/42 HTTP/1.1" 200 5537 "-" "Mozilla/5.0 (Macintosh) Safari/605.1.15" $ ls -l access.log -rw-r--r-- 1 jano jano 15139912 Oct 22 23:59 access.log $ gzip access.log $ ls -l access.log* -rw-r--r-- 1 jano jano 1541139 Oct 22 23:59 access.log.gz
Decompressing
The gunzip command, or gzip -d, restores the
original file and removes the compressed one.
gunzip access.log.gz ls -l access.log*
The size and the timestamp are the same as before compression. Like compression, decompression works in place, which matters when disk space is tight: during the operation, both files exist.
$ gunzip access.log.gz $ ls -l access.log* -rw-r--r-- 1 jano jano 15139912 Oct 22 23:59 access.log
Keeping the original and verbose output
The -k option keeps the input file, and -v
reports the compression ratio.
gzip -k -v access.log gpl.txt ls -l access.log* gpl.txt* gzip access.log
The ratio is the space saved: 89.8% for the log, 65.5% for the text of the
GPL license, which is ordinary English prose. gzip refuses
to overwrite an existing compressed file; in a terminal, it asks first.
The -f option forces the overwrite.
$ gzip -k -v access.log gpl.txt access.log: 89.8% -- created access.log.gz gpl.txt: 65.5% -- created gpl.txt.gz $ ls -l access.log* gpl.txt* -rw-r--r-- 1 jano jano 15139912 Oct 22 23:59 access.log -rw-r--r-- 1 jano jano 1541139 Oct 22 23:59 access.log.gz -rw-r--r-- 1 jano jano 35149 Oct 20 10:00 gpl.txt -rw-r--r-- 1 jano jano 12138 Oct 20 10:00 gpl.txt.gz $ gzip access.log gzip: access.log.gz already exists; not overwritten
Writing to standard output
The -c option writes the result to standard output and leaves
the input alone. Without a file name, gzip compresses its
standard input.
gzip -c gpl.txt > license.gz ls -l gpl.txt license.gz ls -l /etc | gzip > etc-listing.gz gzip -dc license.gz | head -n 3
With -c, we can choose the output name freely. Compressing a
pipe is useful for output that would take much space, such as database
dumps: pg_dump mydb | gzip > mydb.sql.gz.
gzip -dc decompresses to standard output.
$ gzip -c gpl.txt > license.gz
$ ls -l gpl.txt license.gz
-rw-r--r-- 1 jano jano 35149 Oct 20 10:00 gpl.txt
-rw-r--r-- 1 jano jano 12138 Oct 23 21:59 license.gz
$ ls -l /etc | gzip > etc-listing.gz
$ gzip -dc license.gz | head -n 3
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Listing compressed files
The -l option shows the compressed and uncompressed sizes
without decompressing.
gzip -l access.log.gz gpl.txt.gz license.gz gzip -lv access.log.gz gzip -lN license.gz
The uncompressed names are derived from the names of the compressed files.
The header, however, stores the original name: license.gz was
created from gpl.txt, which -N shows.
gunzip -N license.gz would restore it under that name. With
-v, the method, the CRC checksum, and the stored time are
shown too. The gzip format stores
the uncompressed size in 32 bits, so for files of 4 GiB or more, the
listed size is wrong.
$ gzip -l access.log.gz gpl.txt.gz license.gz
compressed uncompressed ratio uncompressed_name
1541139 15139912 89.8% access.log
12138 35149 65.5% gpl.txt
12138 35149 65.5% license
1565415 15210210 89.7% (totals)
$ gzip -lv access.log.gz
method crc date time compressed uncompressed ratio uncompressed_name
defla 9993cffb Oct 22 23:59 1541139 15139912 89.8% access.log
$ gzip -lN license.gz
compressed uncompressed ratio uncompressed_name
12138 35149 65.5% gpl.txt
Reading compressed files directly
Tools with a z prefix work on compressed files without
creating a decompressed copy: zcat, zless,
zgrep, zdiff, and others.
zcat access.log.gz | head -n 2
zgrep -c '" 404 ' access.log.gz
zcat access.log.gz | awk '{print $9}' | sort | uniq -c | sort -rn
zdiff license.gz gpl.txt && echo "same content"
zgrep takes the same options as grep; here it
counts the requests with status 404. Rotated logs in
/var/log are kept compressed, and these tools search them in
place. zcat also handles uncompressed files when given
-f, and zgrep does so by default.
$ zcat access.log.gz | head -n 2
192.0.2.176 - - [21/Oct/2026:00:00:00 +0000] "GET /about HTTP/1.1" 200 32027 "-" "Mozilla/5.0 (Macintosh) Safari/605.1.15"
192.0.2.93 - - [21/Oct/2026:00:00:00 +0000] "GET /products/42 HTTP/1.1" 200 5537 "-" "Mozilla/5.0 (Macintosh) Safari/605.1.15"
$ zgrep -c '" 404 ' access.log.gz
10902
$ zcat access.log.gz | awk '{print $9}' | sort | uniq -c | sort -rn
65452 200
21892 304
10902 404
10880 301
10874 500
$ zdiff license.gz gpl.txt && echo "same content"
same content
Compression levels
The options -1 to -9 set the compression level.
-1 (or --fast) is the fastest, -9
(--best) compresses best; the default is 6.
for l in 1 6 9; do printf -- "-%s: " $l; gzip -$l -c gpl.txt | wc -c; done zcat access.log.gz > plain.log time gzip -1 -c plain.log > fast.gz time gzip -9 -c plain.log > best.gz ls -l fast.gz best.gz access.log.gz
For the license text, levels 6 and 9 give almost the same size. For the
log, level 9 saves 12% compared with level 6 and takes more than five
times longer than level 1. Decompression speed does not depend on the level.
The default is a good compromise; use -9 for files that are
compressed once and downloaded many times.
$ for l in 1 6 9; do printf -- "-%s: " $l; gzip -$l -c gpl.txt | wc -c; done -1: 14229 -6: 12138 -9: 12132 $ zcat access.log.gz > plain.log $ time gzip -1 -c plain.log > fast.gz real 0m0.093s user 0m0.090s sys 0m0.003s $ time gzip -9 -c plain.log > best.gz real 0m0.535s user 0m0.530s sys 0m0.004s $ ls -l fast.gz best.gz access.log.gz -rw-r--r-- 1 jano jano 1541139 Oct 22 23:59 access.log.gz -rw-r--r-- 1 jano jano 1359039 Oct 23 21:59 best.gz -rw-r--r-- 1 jano jano 2187256 Oct 23 21:59 fast.gz
Multiple files and directories
Given several files, gzip compresses each of them separately.
The -r option walks directories.
cp gpl.txt a.txt; cp gpl.txt b.txt gzip a.txt b.txt ls a.txt* b.txt* gzip -r project find project -type f gunzip -r project
The result is one .gz file per input, not a single archive.
gzip -r replaces every file in the tree with its compressed
version, which is rarely what we want. To pack a directory into one file,
use tar, as shown below.
$ cp gpl.txt a.txt; cp gpl.txt b.txt $ gzip a.txt b.txt $ ls a.txt* b.txt* a.txt.gz b.txt.gz $ gzip -r project $ find project -type f project/LICENSE.gz project/docs/services.txt.gz project/src/f2.c.gz project/src/f1.c.gz project/src/f3.c.gz $ gunzip -r project
Concatenated gzip files
A gzip file can consist of several members, and concatenating gzip files produces a valid gzip file.
echo "first part" | gzip > part1.gz echo "second part" | gzip > part2.gz cat part1.gz part2.gz > joined.gz zcat joined.gz gzip -l joined.gz
Decompression yields both parts in order. This lets programs append
compressed chunks to a file. gzip -l, however, reports only the
size of the last member, 12 bytes, and a meaningless negative ratio.
$ echo "first part" | gzip > part1.gz
$ echo "second part" | gzip > part2.gz
$ cat part1.gz part2.gz > joined.gz
$ zcat joined.gz
first part
second part
$ gzip -l joined.gz
compressed uncompressed ratio uncompressed_name
63 12 -425.0% joined
Testing integrity
Every gzip file contains a CRC-32 checksum of the data. The
-t option decompresses in memory and checks it.
gzip -t access.log.gz; echo $? head -c 300000 access.log.gz > broken.gz gzip -t broken.gz; echo $? zcat broken.gz | wc -l
A good file passes silently with status 0. The truncated copy, like an
interrupted download, fails with unexpected end of file and
status 1. zcat still outputs what it could decompress, 23,302
complete lines, so data before the damaged point can be recovered.
$ gzip -t access.log.gz; echo $? 0 $ head -c 300000 access.log.gz > broken.gz $ gzip -t broken.gz; echo $? gzip: broken.gz: unexpected end of file 1 $ zcat broken.gz | wc -l gzip: broken.gz: unexpected end of file 23302
Data that does not compress
Compression works by finding repetition. Random data, and data that is already compressed, such as JPEG images, videos, or ZIP files, has none.
ls -l random.bin gzip -k -v random.bin xz -k -v random.bin ls -l random.bin*
Both compressed files are slightly larger than the original, by the size of their headers. Compressing such files again only costs time.
$ ls -l random.bin -rw-r--r-- 1 jano jano 1000000 Oct 23 21:59 random.bin $ gzip -k -v random.bin random.bin: -0.0% -- created random.bin.gz $ xz -k -v random.bin random.bin: 976.7 KiB / 976.6 KiB = 1.000 $ ls -l random.bin* -rw-r--r-- 1 jano jano 1000000 Oct 23 21:59 random.bin -rw-r--r-- 1 jano jano 1000184 Oct 23 21:59 random.bin.gz -rw-r--r-- 1 jano jano 1000108 Oct 23 21:59 random.bin.xz
Name and timestamp in the header
By default, gzip stores the original file name and
modification time in the header. The -n option leaves them
out.
gzip -c gpl.txt | md5sum touch gpl.txt gzip -c gpl.txt | md5sum gzip -cn gpl.txt | md5sum touch -d '2020-01-01' gpl.txt gzip -cn gpl.txt | md5sum
Touching the file changes the compressed output, although the content is
the same. With -n, the output depends only on the content.
This matters for reproducible builds and for caches that compare
checksums of compressed files. When decompressing, -N
restores the stored name and timestamp.
$ gzip -c gpl.txt | md5sum ad7f4f078004f6b963f80cacc84d61af - $ touch gpl.txt $ gzip -c gpl.txt | md5sum 8df03619d94d4763767c4cfd39ed0a9f - $ gzip -cn gpl.txt | md5sum 7aa57b7a02bdafacf2d797f6eda77f2c - $ touch -d '2020-01-01' gpl.txt $ gzip -cn gpl.txt | md5sum 7aa57b7a02bdafacf2d797f6eda77f2c -
Compressing with xz
The xz command works like gzip: it replaces the
file with a .xz version, and accepts -k,
-c, -d, -t, -v, and
-l.
zcat access.log.gz > access.log xz -k -v access.log ls -l access.log access.log.xz access.log.gz xz -l access.log.xz
The xz file is 22% smaller than the gzip one, but
compression took five seconds instead of a fraction of a second. The
ratio in xz output is the compressed size divided by the
original, 0.080, the opposite of gzip's space saved.
xz protects the data with a CRC64 checksum.
$ zcat access.log.gz > access.log
$ xz -k -v access.log
access.log: 1,176.1 KiB / 14.4 MiB = 0.080, 2.7 MiB/s, 0:05
$ ls -l access.log access.log.xz access.log.gz
-rw-r--r-- 1 jano jano 15139912 Oct 23 21:59 access.log
-rw-r--r-- 1 jano jano 1541139 Oct 22 23:59 access.log.gz
-rw-r--r-- 1 jano jano 1204348 Oct 23 21:59 access.log.xz
$ xz -l access.log.xz
Strms Blocks Compressed Uncompressed Ratio Check Filename
1 1 1,176.1 KiB 14.4 MiB 0.080 CRC64 access.log.xz
Decompressing and reading xz files
unxz is the same as xz -d, and
xzcat as xz -dc. The xzgrep,
xzless, and xzdiff commands correspond to their
z counterparts.
xzcat access.log.xz | tail -n 1 xzgrep -c 'curl/' access.log.xz xz -t access.log.xz && echo OK mv access.log access.log.orig unxz access.log.xz cmp access.log access.log.orig && echo identical
The round trip restores the file byte for byte, as cmp
confirms. Decompressing xz is much faster than compressing
it.
$ xzcat access.log.xz | tail -n 1 198.51.100.254 - - [22/Oct/2026:09:23:28 +0000] "POST /static/style.css HTTP/1.1" 200 40020 "-" "Mozilla/5.0 (Macintosh) Safari/605.1.15" $ xzgrep -c 'curl/' access.log.xz 30099 $ xz -t access.log.xz && echo OK OK $ mv access.log access.log.orig $ unxz access.log.xz $ cmp access.log access.log.orig && echo identical identical
xz levels, extreme mode, and threads
xz has levels 0 to 9 (default 6) and the -e
(extreme) modifier, which tries harder at the same memory use. The
-T option sets the number of threads; -T0 uses as
many as there are processor cores.
time xz -6 -T1 -c access.log > a6.xz time xz -9e -T1 -c access.log > a9e.xz time xz -6 -T0 -c access.log > a6t.xz time xz -6 -T0 --block-size=2MiB -c access.log > a6b.xz ls -l a6.xz a9e.xz a6t.xz a6b.xz xz -lvv a9e.xz | grep -i memory
-9e saves another 6% and takes twice as long. -T0
alone did not help: in threaded mode, xz splits the input into
blocks and compresses them in parallel, but the default block size at level
6 is 24 MiB, three times the dictionary size, so our 14 MiB file is a
single block. With 2 MiB blocks, compression ran almost six times faster
on eight cores, and the file grew by less than 1%, because each block is
compressed independently. Files compressed in blocks can also be
decompressed in parallel. Level 9 needs a 64 MiB dictionary; the
decompressor needs 65 MiB of memory, and the compressor several hundred
MiB per thread.
$ time xz -6 -T1 -c access.log > a6.xz real 0m5.264s user 0m5.226s sys 0m0.037s $ time xz -9e -T1 -c access.log > a9e.xz real 0m9.872s user 0m9.809s sys 0m0.062s $ time xz -6 -T0 -c access.log > a6t.xz real 0m5.236s user 0m5.195s sys 0m0.048s $ time xz -6 -T0 --block-size=2MiB -c access.log > a6b.xz real 0m0.909s user 0m5.835s sys 0m0.099s $ ls -l a6.xz a9e.xz a6t.xz a6b.xz -rw-r--r-- 1 jano jano 1212608 Oct 23 21:59 a6b.xz -rw-r--r-- 1 jano jano 1204356 Oct 23 21:59 a6t.xz -rw-r--r-- 1 jano jano 1204348 Oct 23 21:59 a6.xz -rw-r--r-- 1 jano jano 1129112 Oct 23 21:59 a9e.xz $ xz -lvv a9e.xz | grep -i memory Memory needed: 65 MiB
Comparing compressors
The following script compresses the log with several tools and levels, and measures the size and the compression and decompression times.
cat compare.sh bash compare.sh access.log
TIMEFORMAT=%R makes the time keyword print only
the elapsed seconds. The results depend heavily on the data. For this log,
bzip2 compresses best, as its block-sorting algorithm suits
text with many repeated fields, but it decompresses slowly.
zstd, the newest of the tools, is the fastest at both ends at
its default level, and at level 19 compresses better than
xz -6 while decompressing about five times faster. gzip
remains the most compatible choice. For a single file, the choice is a
trade-off between size, speed, and what the recipients have installed.
$ cat compare.sh
#!/bin/bash
file=$1
size=$(stat -c %s "$file")
TIMEFORMAT=%R
printf '%-10s %10s %7s %8s %8s\n' tool bytes ratio "comp s" "decomp s"
for cmd in "gzip -6" "gzip -9" "bzip2 -9" "zstd -3" "zstd -19" "xz -6"; do
tool=${cmd%% *}
ctime=$( { time $cmd -c "$file" > out.tmp; } 2>&1 )
csize=$(stat -c %s out.tmp)
dtime=$( { time $tool -dc out.tmp > /dev/null; } 2>&1 )
ratio=$(awk -v a=$csize -v b=$size 'BEGIN { printf "%.1f%%", 100 * a / b }')
printf '%-10s %10d %7s %8s %8s\n' "$cmd" "$csize" "$ratio" "$ctime" "$dtime"
done
rm -f out.tmp
$ bash compare.sh access.log
tool bytes ratio comp s decomp s
gzip -6 1541139 10.2% 0.200 0.043
gzip -9 1359040 9.0% 0.537 0.043
bzip2 -9 868070 5.7% 1.405 0.259
zstd -3 1921824 12.7% 0.037 0.014
zstd -19 1157314 7.6% 8.186 0.013
xz -6 1204348 8.0% 5.406 0.061
Compressed tar archives
tar calls gzip with the -z option and
xz with -J. When extracting or listing, GNU tar
detects the compression automatically.
tar -czf project.tar.gz project tar -cJf project.tar.xz project ls -l project.tar.* tar -tf project.tar.xz tar -cf - project | xz -9 -T0 > project-max.tar.xz ls -l project-max.tar.xz
For options that tar does not pass on, such as levels and
threads, pipe the archive through the compressor. With -9 and
many threads, xz would need more memory than its default
limit, which is a quarter of the RAM for threaded mode, so it reduced the
number of threads by itself. For this small project, the extra effort
gained nothing. See the tar command
article for more.
$ tar -czf project.tar.gz project $ tar -cJf project.tar.xz project $ ls -l project.tar.* -rw-r--r-- 1 jano jano 17822 Oct 23 22:00 project.tar.gz -rw-r--r-- 1 jano jano 16452 Oct 23 22:00 project.tar.xz $ tar -tf project.tar.xz project/ project/LICENSE project/docs/ project/docs/services.txt project/src/ project/src/f3.c project/src/f1.c project/src/f2.c $ tar -cf - project | xz -9 -T0 > project-max.tar.xz xz: Reduced the number of threads from 8 to 3 to not exceed the memory usage limit of 3,947 MiB $ ls -l project-max.tar.xz -rw-r--r-- 1 jano jano 16460 Oct 23 22:00 project-max.tar.xz
Common errors
The following commands show typical error messages.
gzip -d gpl.txt gunzip README echo "plain text" > notes.gz gunzip notes.gz; echo $? gzip gpl.txt gzip -f gpl.txt ls gpl.txt*
gzip -d only accepts known suffixes such as
.gz, .tgz, and .z; for others,
decompress with -c to a file of our choice. Given a name
without a suffix, gunzip looks for the .gz file.
A file with a .gz suffix is not necessarily compressed;
gzip checks the magic number at the start of the file.
-f overwrites the existing compressed file.
$ gzip -d gpl.txt gzip: gpl.txt: unknown suffix -- ignored $ gunzip README gzip: README.gz: No such file or directory $ echo "plain text" > notes.gz $ gunzip notes.gz; echo $? gzip: notes.gz: not in gzip format 1 $ gzip gpl.txt gzip: gpl.txt.gz already exists; not overwritten $ gzip -f gpl.txt $ ls gpl.txt* gpl.txt.gz
Summary
| Task | gzip | xz |
|---|---|---|
| Compress, replacing the file | gzip file | xz file |
| Keep the original | gzip -k file | xz -k file |
| Decompress | gunzip file.gz | unxz file.xz |
| To standard output | gzip -c, zcat | xz -c, xzcat |
| List sizes | gzip -l | xz -l |
| Test | gzip -t | xz -t |
| Search | zgrep | xzgrep |
| Level | -1 to -9 | -0 to -9, -e |
| Threads | none (use pigz) | -T0 |
| With tar | tar -czf | tar -cJf |
Source
In this article, we have compressed and decompressed files with
gzip and xz, read compressed files with
zcat and zgrep, compared compression levels,
threads, and tools, tested integrity, and created compressed tar archives.
See also the zip command article.
Author
List all Linux tutorials.