zlib.gunzipSync()

Decompresses gzip-formatted data synchronously and returns the original bytes as a Buffer.

Signature
zlib.gunzipSync(buffer[, options])
  • Added in Node.js v0.11.3

Parameters

ParameterTypeDescription
$bufferBuffer | TypedArray | DataView | ArrayBuffer | stringGzip-compressed data to expand.
$optionsoptionalObjectzlib options: `windowBits`, `chunkSize`, `flush`, `finishFlush`, `dictionary`, `info` and `maxOutputLength`.

Return value

BufferA Buffer holding the decompressed data. With `options.info` set to `true`, an object `{ buffer, engine }` is returned instead.

Errors

Throws synchronously when the input is not valid gzip data, when the CRC or length trailer does not match, or `ERR_BUFFER_TOO_LARGE` when the output would exceed `maxOutputLength`.

Changelog

  • PHP v5.11.0The `finishFlush` option is supported, which allows truncated input to be decoded to the point it was cut off.
  • PHP v14.5.0, v12.19.0The `maxOutputLength` option is supported, capping how large the decompressed result may grow.

zlib.gunzipSync() is the inverse of zlib.gzipSync(). It reads a complete gzip member — magic bytes, header, deflate stream, CRC32 and length trailer — and returns the original bytes. It verifies the checksum, so a corrupted payload raises an error rather than returning garbage.

When to reach for it

Reach for it when you already hold the entire compressed payload in memory: a .gz file read with fs.readFileSync(), a row pulled from a database, a cached HTTP body. For anything arriving over a socket or read off disk incrementally, use zlib.createGunzip() so you never buffer the whole thing.

What trips people up

  • Decompression bombs. A few kilobytes can expand to gigabytes. Any input you did not compress yourself should be decompressed with maxOutputLength set, which turns overflow into a catchable ERR_BUFFER_TOO_LARGE.
  • Wrong format. Feeding a raw zlib or deflate stream here fails with Z_DATA_ERROR; those need inflateSync() or inflateRawSync(). Check for the 1f 8b prefix if you are unsure what you have.
  • Truncated input. A partial gzip stream throws by default. Setting finishFlush: zlib.constants.Z_SYNC_FLUSH recovers whatever decoded successfully, at the cost of skipping the checksum.

Multiple gzip members concatenated into one buffer are decoded as a single continuous stream.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Read a .gz log file into memory and parse it in one pass
  • Expand a gzip-compressed column fetched from Postgres or MySQL
  • Unpack a cached HTTP response body stored with Content-Encoding: gzip
  • Verify in a test that a compression step round-trips exactly

Frequently asked questions

How do I decompress a gzip Buffer in Node.js?

Call zlib.gunzipSync(buffer), which returns the original bytes as a Buffer — add .toString() if the content was text. For data arriving in chunks, pipe through zlib.createGunzip() instead.

Why does gunzipSync throw incorrect header check?

That Z_DATA_ERROR means the input is not gzip format — most often it is a raw zlib stream, which needs inflateSync(), or a raw deflate stream, which needs inflateRawSync(). Gzip data always starts with the bytes 1f 8b.

How do I stop a gzip bomb from crashing my server?

Pass maxOutputLength with a byte budget, for example zlib.gunzipSync(input, { maxOutputLength: 1024 * 1024 }), and Node throws ERR_BUFFER_TOO_LARGE instead of allocating. Always set it for input you did not compress yourself.

More examples

Read a .gz file and count its lines

The whole archive is expanded into one string before splitting. That is fine for a small log, but a multi-gigabyte .gz would exhaust memory here — switch to createGunzip() piped into a line reader at that size.

Example · javascript
const zlib = require('node:zlib');
const fs = require('node:fs');

// Create the archive so the example is self-contained.
const lines = Array.from({ length: 500 }, (_, i) => `GET /page/${i} 200`);
fs.writeFileSync('access.log.gz', zlib.gzipSync(lines.join('\n')));

const text = zlib.gunzipSync(fs.readFileSync('access.log.gz')).toString('utf8');
console.log('file bytes:', fs.statSync('access.log.gz').size);
console.log('lines:', text.split('\n').length);
console.log('first:', text.split('\n')[0]);

fs.unlinkSync('access.log.gz');

Decompress untrusted input safely

Two guards in sequence: a magic-byte check that rejects non-gzip input before any allocation, and a size cap that converts a decompression bomb into a plain error. The second call prints the blocked message because four megabytes of zeroes exceed the 64 KB budget.

Example · javascript
const zlib = require('node:zlib');

function safeGunzip(payload, limitBytes = 1024 * 1024) {
  if (payload.length < 2 || payload[0] !== 0x1f || payload[1] !== 0x8b) {
    throw new Error('not a gzip payload');
  }
  try {
    return zlib.gunzipSync(payload, { maxOutputLength: limitBytes });
  } catch (err) {
    if (err.code === 'ERR_BUFFER_TOO_LARGE') {
      throw new Error(`payload expands beyond ${limitBytes} bytes`);
    }
    throw err;
  }
}

console.log(safeGunzip(zlib.gzipSync('small and safe')).toString());

try {
  safeGunzip(zlib.gzipSync(Buffer.alloc(4 * 1024 * 1024)), 64 * 1024);
} catch (err) {
  console.log('blocked:', err.message);
}

Recover data from a truncated stream

By default a truncated gzip stream throws, because the CRC trailer is missing. Z_SYNC_FLUSH tells zlib to return everything it managed to decode and stop cleanly. The recovered text is valid but unverified, so treat it as best-effort salvage, never as trusted input.

Example · javascript
const zlib = require('node:zlib');

const full = zlib.gzipSync('alpha beta gamma delta epsilon '.repeat(100));
const cut = full.subarray(0, full.length - 40); // simulate a broken download

try {
  zlib.gunzipSync(cut);
} catch (err) {
  console.log('strict mode fails:', err.code);
}

const partial = zlib.gunzipSync(cut, {
  finishFlush: zlib.constants.Z_SYNC_FLUSH
});
console.log('recovered bytes:', partial.length);
console.log('tail:', JSON.stringify(partial.toString().slice(-20)));

Discussion

  • Be the first to comment on this lesson.