File & PDF Tools

GZIP Compress / Decompress

Compress one file as .gz or restore GZIP content.

GZIP compress / decompress
Compress one file or restore a .gz file.
Local processing

GZIP Compress / Decompress

Compress one file as .gz or restore GZIP data. Unlike ZIP, GZIP does not contain a multi-file directory.

Usage

  • Compression levels range from 0 to 9
  • The decompressed name is inferred from the .gz filename
  • The format does not retain the original MIME type

Compresses one file into a standard .gz stream inside the browser and restores .gz input back to its original bytes. Useful for checking what a compressed HTTP response or build artifact really contains, and for producing a .gz for a static server when no shell is at hand.

What the 10-byte header and 8-byte trailer encode

Every file this tool writes begins with 1f 8b 08: two magic bytes and CM = 8, announcing an RFC 1951 DEFLATE stream. Byte 3 is FLG, left at zero here, so no optional original filename or comment field follows. Bytes 4 to 7 carry MTIME as a 32-bit Unix second count in little-endian order, byte 8 is XFL, and byte 9 is OS. Inside, DEFLATE slides a 32 KB window and emits LZ77 matches of 3 to 258 bytes, then Huffman-codes the result. The payload is followed by a CRC-32 of the uncompressed data and ISIZE, the original length modulo 2^32, both little-endian, so an input beyond 4 GiB would report a wrapped size.

Why level 0 makes the file bigger

Level 0 does not mean gzip without compression: it emits DEFLATE stored blocks, five bytes of block framing per 65,535-byte chunk, on top of the 10-byte header and 8-byte trailer, so the result is always larger than the source. Measured on a 2,391-byte JSON array of 40 near-identical records, level 0 produced 2,414 bytes while levels 1, 2, 6 and 9 all produced exactly 188 bytes, because a short repetitive payload exhausts the matcher on its first pass. Level differences surface on long text with scattered repetition, where 9 over 6 usually buys a couple of percent for several times the CPU. Incompressible bytes cannot shrink at all: 5,000 random bytes at level 9 became 5,023.

Three wrappers around one DEFLATE core

Raw DEFLATE (RFC 1951), zlib (RFC 1950, two header bytes plus an Adler-32) and gzip (RFC 1952, 10-byte header plus a CRC-32) hold identical compressed bits in different envelopes. An HTTP response labelled Content-Encoding: gzip uses the RFC 1952 form, which is what this tool reads and writes; PNG and ZIP files use zlib or raw DEFLATE internally, so they are not gzip streams despite all three being compressed. Mixing envelopes yields invalid gzip data or incorrect header check: strip the 10-byte header to get zlib, strip two more bytes to get raw DEFLATE. Since a .gz holds no directory, a.tar.gz also restores to a.tar rather than extracted files.

Worked example

Compressing a 12-byte UTF-8 text file at level 6

Input
hello.txt, whose content is hello 世界 (12 UTF-8 bytes)
Output
hello.txt.gz — 32 bytes
1f 8b 08 00 [MTIME, 4 bytes] 00 03
14 bytes of DEFLATE data
6e 28 2e b2 0c 00 00 00

The trailer is the verifiable part: 6e 28 2e b2 read little-endian is 0xb22e286e, the CRC-32 of those 12 bytes, and 0c 00 00 00 is 12, the size modulo 2^32. XFL is 00 because level 6 sits between the level-1 and level-9 markers, and OS reads 03 for Unix. Re-running the same compression changes only the four MTIME bytes, which hold the time of compression.

Limits and pitfalls

  • One input file per run, at most 100 MB, and the decompressed result must stay at or below 500 MB; both directions run synchronously on the main thread, so a large file blocks the tab until it finishes.
  • The level must be an integer from 0 to 9. Level 0 stores the data uncompressed yet still pays for framing, and even an empty file comes out as 20 bytes: 10 header bytes, an empty DEFLATE block and the 8-byte trailer.
  • A multi-member .gz, which is what concatenating two gzip files produces, decodes to its first member here only: joining members holding AAA and BBB returned AAA, whereas gzip -dc prints AAABBB. A truncated .gz fails with unexpected EOF instead of partial output.
  • The original filename is never stored in the header (FLG stays 0) and MTIME records the compression time, so identical input compressed twice gives different bytes; the download name comes from the file you picked, not from inside the .gz.
  • gzip compresses a single stream and knows nothing about archives: a .tar.gz gives back a .tar file, and already-compressed data grows, as 5,000 random bytes becoming 5,023 shows.

Related tools

When it goes wrong

I dropped a .gz file in and it still says Compress, so I end up with report.json.gz.gz that nothing will open.

CauseThe direction is decided by the filename alone: selectFiles() lowercases the name and checks whether it ends with .gz, and nothing inspects the 1f 8b magic bytes. A file renamed by a download (payload.gz.txt) or saved without a suffix falls into the compress branch, where gzipSync wraps bytes that are already a gzip stream.

FixClick the Decompress .gz segment by hand, or rename the file so the name ends in .gz before dropping it. Check the filename on the result panel after every run: a second .gz means the wrong branch ran and the download is a gzip of a gzip.

The panel says the file is ready, but the restored file is short: half a JSON document, or a PNG that will not open.

CausegunzipSync reads the last four bytes of the file as ISIZE, allocates an output buffer of exactly that length and returns only the bytes actually written, so a wrong ISIZE truncates the result without an error. The CRC-32 just before it is never compared either: a .gz whose CRC field is overwritten still decodes and still reports success.

FixRun gzip -t on the stream before trusting it, or hash the restored file and the original with the file-hash tool. Read "The output file is ready." as "bytes were produced", not as "the checksum matched".

Dragging the compression level from 0 to 9 while decompressing changes nothing at all.

CauseThe level slider renders whenever a file is selected and the mode is not zip-extract, so it stays on screen in decompress mode, yet that branch calls gunzipSync(source) with no options. Block types and Huffman tables are already fixed inside the DEFLATE bits; level is a compressor-side knob with no meaning on inflate.

FixSet the level only under the Compress segment. To prove a restored file matches the original, run both through the file-hash tool instead of re-running with a different level.

A 120 MB file selects without a word of warning, and the 100 MB limit only shows up after I press Compress GZIP.

CauseThe selection step runs assertTotalSize(), which checks the 250 MB total, so one oversized file passes easily. The 100 MB cap lives in readFileBytes() and throws FILE_TOO_LARGE at processing time, which the status line then renders as "Each file must be 100 MB or smaller."

FixSplit first with split -b 100M (100 MiB = 104,857,600 bytes) and compress the pieces one at a time, or run gzip -9 on the machine holding the file. Anything above 104,857,600 bytes never gets compressed on this page.

After switching to Decompress .gz, my backup.tgz is greyed out in the file picker and cannot be selected.

CauseIn decompress mode the dropzone sets accept to .gz,application/gzip. Files named .tgz or .svgz, and gzip files a Linux box reports as application/x-gzip, match neither the extension nor the MIME entry, so pickers that honour accept disable them.

FixDrop the file onto the page instead of using the picker, since drag and drop ignores accept; or rename backup.tgz to backup.tar.gz, which changes no bytes. A .tgz is already a gzip wrapper around a tar, so the restored file is a .tar either way.

One concrete scenario

Two files, one cap: pre-compressing a 1 MiB bundle for gzip_static

dist/app.js is 1,048,576 bytes and dist/app.css is 262,144. The result panel formats sizes through formatBytes: below 1,048,576 it prints KB with one decimal, so the CSS reads 256.0 KB while the JS reads 1.00 MB, the first reading that can land in the MB row. The tool takes one file per run, so two runs give app.js.gz and app.css.gz, the exact pairs nginx gzip_static looks for beside the originals; level 6, where the slider starts, is enough for text. Both inputs sit far under the 104,857,600-byte input cap, and unpacking either stays below 524,288,000 restored bytes, so neither needs splitting. A source above the input cap refuses to compress at all, and that job belongs to gzip -9 -n on the server.

Frequently Asked Questions

What compression ratio does GZIP typically achieve?
Text formats (HTML, CSS, JS, JSON) usually shrink to 20–30% of original size — the more repetition, the better. Already-compressed formats (JPG, PNG, ZIP, MP4) barely shrink and can even grow, which is why CDNs compress only text responses.
GZIP versus ZIP — what is the difference?
GZIP compresses a single stream with no file directory — paired with tar as .tar.gz for archives; ZIP carries its own multi-file structure. Web transfer uses GZIP (HTTP content encoding); file bundles use ZIP or tar.
Is level 9 always better than level 6?
Size gains are usually under 1–2% while time can double. For on-the-fly server compression pick 4–6; for build artifacts (compressed once before release) use 9. The tool lets you compare 0–9 empirically before committing.

Content reviewed: