XZ file format
XZ-compressed data, typically using the LZMA2 algorithm.
- MIME type
- application/x-xz
- Extensions
- .xz
- Category
- Archives & compression
Magic-number signature
| 0x00000000 | FD377A585A00 | ·7zXZ· | XZ magic |
About the XZ format
XZ wraps LZMA2 in a container with integrity checks, giving high compression ratios for software distribution and kernel images. The six-byte magic is FD 37 7A 58 5A 00. Unlike the formats it replaced, xz is a proper container rather than a header over a stream: it has a stream header and a matching stream footer, one or more blocks each with their own header and CRC, and an index at the end listing every block's compressed and uncompressed size. That index is what lets a reader verify a file, or seek in one, without decompressing it — and it is also why a truncated .xz cannot be checked at all.
Header layout, byte by byte
| Offset | Length | Field | Meaning |
|---|---|---|---|
| 0 | 6 | Header magic bytes | FD 37 7A 58 5A 00. Bytes 1 through 4 read '7zXZ' in ASCII, which is a persistent source of confusion with 7-Zip's own format; the leading FD is deliberately outside ASCII and the trailing 00 makes the file fail loudly in any text-mode transfer. |
| 6 | 2 | Stream flags | The first byte is reserved and must be 00. The low four bits of the second name the integrity check: 0 none, 1 CRC-32, 4 CRC-64, 10 SHA-256. A file written by the standard tool reads 00 04, because CRC-64 is the default. |
| 8 | 4 | CRC-32 of the stream flags | Little-endian, covering only the two bytes at offset 6. It exists so a reader can trust which check type to apply before it has decoded anything. |
| 12 | 1 | Block header size | Encoded: the real size is this value plus one, times four, so 04 means a twenty-byte block header. A 00 here is special — it means there are no more blocks and the index begins at this offset instead. |
| 13 | 1 | Block flags | The low two bits are the number of filters minus one; bit 6 says a compressed-size field follows and bit 7 an uncompressed-size field. A typical single-filter block with both sizes present reads C0. |
| variable | variable | Block sizes and filter flags | Inside the block header, in order: the optional compressed and uncompressed sizes as multibyte variable-length integers, then the filter chain — LZMA2 is filter id 0x21 with a single property byte encoding the dictionary size — then zero padding to a multiple of four and a CRC-32 of the block header itself. |
| variable | variable | Index | After the last block: a record per block giving its compressed and uncompressed size, padded to a multiple of four and followed by its own CRC-32. This is what `xz --list` reads, and it is why xz can report a file's contents without decompressing it. |
| -12 | 4 | Stream footer CRC-32 | Little-endian, over the six footer bytes that follow it. The footer is a fixed twelve bytes, so unlike ZIP's end record there is never any scanning backwards to find it. |
| -8 | 4 | Backward size | Little-endian; the real index size is this value plus one, times four. A file whose index is twelve bytes stores 02 00 00 00. It is what makes the index findable from the end of the file, and therefore what makes the whole format seekable. |
| -4 | 2 | Stream flags (copy) | A byte-for-byte duplicate of offsets 6 and 7, so a decoder that started from the end learns the same check type the header declared. If the two disagree, the file has been tampered with or spliced. |
| -2 | 2 | Footer magic 'YZ' | 59 5A. Every intact .xz ends with these two bytes; if they are not the last two, the file is truncated, and no amount of valid-looking data at the front changes that. |
Telling it apart from its neighbours
| Easily confused with | How to tell |
|---|---|
| 7-Zip | The confusion the format invited on itself. An .xz begins FD 37 7A 58 5A 00, in which '7z' sits at offsets 1 and 2; a real .7z has 37 7A BC AF 27 1C at offset 0. So the same two bytes appear in both, one position apart, and a sloppy search for '7z' matches both. They are unrelated formats: .7z is a multi-file archive with a directory, .xz is a single compressed stream. |
| lzma (LZMA_Alone) | The predecessor, and the reason .lzma files are so hard to identify: they have no magic number at all. The header is thirteen bytes — one properties byte, typically 5D, then a four-byte little-endian dictionary size, then an eight-byte little-endian uncompressed size that is FF repeated when unknown. A real file reads 5D 00 00 80 00 followed by eight FF bytes, which is the closest thing to a signature it has. |
| GZIP | 1F 8B against FD 37 7A 58 5A 00, and structurally further apart than that suggests: gzip is a header over a stream with no index, while xz keeps a block index at the end and can therefore be listed and verified without decompression. |
| Zstandard | The modern rival. 28 B5 2F FD against xz's magic. At maximum settings xz still compresses smaller, but Zstandard decompresses several times faster and compresses far faster, which is why package managers have been migrating away. |
| TAR | A .tar.xz and an xz of a single file are the same format. Decompress and check offset 257 of the result for 'ustar' inside the first 512-byte tar header — nothing in the .xz container itself distinguishes them. |
How these files break
- Truncation. The index and the footer both live at the end, so a truncated .xz cannot be listed or verified even though every block that arrived is decodable. The tool reports an unexpected end of input, and the two-byte 'YZ' at the end of the file is the fastest way to check for it by eye.
- A flipped bit in a block body. The default integrity check is CRC-64, so the damage is caught — but only after decompression, because the CRC-32 at offset 8 and the block header's own CRC cover metadata only. On badly damaged data LZMA2 usually fails with a decode error before reaching the checksum.
- Stripped trailing NUL bytes. Blocks and streams are padded to multiples of four with zero bytes, so a transfer or a build step that trims trailing NULs destroys the alignment the footer depends on. The file still identifies as xz and fails at the very end.
- Concatenated streams parsed as one. Joining .xz files is legal, optional zero padding is allowed between them, and the standard tool decompresses all of them. A hand-rolled reader that stops at the first footer magic silently drops everything after it.
- Text-mode transfer. The 00 in the header magic is there to make this fail immediately, and it does — but the same translation applied further into the stream leaves the header intact, so the file identifies correctly and only fails when decoding reaches the damage.
Where the format stands today
XZ is what you use when a file is compressed once and downloaded a great many times, and the download is the expensive part. Kernel tarballs, distribution packages, source releases and container base layers have all leaned on it for over a decade, because at high presets LZMA2 still produces the smallest general-purpose output of the common formats. The costs are real: compression is slow and enormously memory-hungry, and decompression memory scales with the dictionary the writer chose, so an archive made at preset 9 needs about 64 MB to open — a genuine problem on embedded targets. That, plus a several-times speed gap, is why Zstandard has been steadily taking its place in package managers and filesystems since about 2019. Where xz remains first choice is bandwidth-bound distribution of static content, and it is a poor choice for anything written repeatedly or streamed live.
Open XZ files on this site
Every one of these runs in your browser — the file never leaves your machine.
Related formats
Formats that are easy to confuse with XZ — compare their leading bytes.