golang/go · error

gzip.ErrHeader

gzip.ErrHeader

Error message

gzip: invalid header

What it means

Returned when the leading bytes of a candidate GZIP stream fail RFC 1952 header validation: magic bytes must be 0x1f 0x8b, the compression method (CM) must be 8 (deflate), reserved FLG bits must be clear, and the MTIME/XFL/OS fields and any optional FEXTRA/FNAME/FCOMMENT/FHCRC sections must parse cleanly. Any deviation aborts the reader before deflate decoding starts.

Source

Thrown at src/compress/gzip/gunzip.go:34

	"time"
)

const (
	gzipID1     = 0x1f
	gzipID2     = 0x8b
	gzipDeflate = 8
	flagText    = 1 << 0
	flagHdrCrc  = 1 << 1
	flagExtra   = 1 << 2
	flagName    = 1 << 3
	flagComment = 1 << 4
)

var (
	// ErrChecksum is returned when reading GZIP data that has an invalid checksum.
	ErrChecksum = errors.New("gzip: invalid checksum")
	// ErrHeader is returned when reading GZIP data that has an invalid header.
	ErrHeader = errors.New("gzip: invalid header")
)

var le = binary.LittleEndian

// noEOF converts io.EOF to io.ErrUnexpectedEOF.
func noEOF(err error) error {
	if err == io.EOF {
		return io.ErrUnexpectedEOF
	}
	return err
}

// The gzip file stores a header giving metadata about the compressed file.
// That header is exposed as the fields of the [Writer] and [Reader] structs.
//
// Strings must be UTF-8 encoded and may only contain Unicode code points
// U+0001 through U+00FF, due to limitations of the GZIP file format.
type Header struct {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Sniff the first two bytes before wrapping: a real GZIP stream starts with 0x1f 0x8b.
  2. Pick the correct decoder for the actual format: use compress/flate for raw deflate, compress/zlib for zlib, or archive/zip for zip.
  3. If the data is plaintext, drop the gzip wrapper entirely and read the underlying io.Reader directly.
  4. Re-check HTTP Content-Encoding / Transfer-Encoding; many servers omit `gzip` when they send identity responses.
  5. For concatenated streams, loop with Multireader semantics or call Reset between members rather than assuming one header covers everything.

Example fix

// before
gr, err := gzip.NewReader(resp.Body)
// "gzip: invalid header" on a plaintext response

// after: sniff magic bytes and degrade gracefully
br := bufio.NewReader(resp.Body)
hdr, _ := br.Peek(2)
var r io.Reader = br
if bytes.Equal(hdr, []byte{0x1f, 0x8b}) {
    gr, err := gzip.NewReader(br)
    if err != nil { return err }
    defer gr.Close()
    r = gr
}
io.Copy(out, r)
Defensive patterns

Strategy: validation

Validate before calling

func isGzipStream(r io.Reader) (io.Reader, bool) {
    br := bufio.NewReader(r)
    b, err := br.Peek(2)
    if err != nil { return br, false }
    return br, b[0] == 0x1f && b[1] == 0x8b
}

// Usage:
//  peeked, ok := isGzipStream(body)
//  if !ok { /* read raw */ }

Try / catch

gr, err := gzip.NewReader(r)
if err != nil {
    if errors.Is(err, gzip.ErrHeader) {
        // Fall back to raw read or try other decoders (zlib, flate).
        return tryOtherFormats(r)
    }
    return err
}

Prevention

When it happens

Trigger: Calling gzip.NewReader (or Read on a reset reader) on a stream whose first ten bytes are not a valid GZIP header. Common offenders: feeding raw deflate (no gzip wrapper), feeding zlib, feeding an uncompressed file, or feeding a zip archive whose first entry happens to look binary.

Common situations: Pointing the reader at a file with the wrong extension (.gz that is actually plaintext, or a .tar that was never gzipped), double-compression where one layer is stripped, content-encoding negotiation bugs in HTTP clients (server sent identity but client assumed gzip), or reading from a multipart body at the wrong offset.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/c3c1d41d65a62b4f. Report an issue: GitHub.