Ffile2fix
Sign in Get started

How to fix ZipArchive::open() error 19 (ER_NOZIP) in PHP

ZipArchive::open() failed with code 19 (ZipArchive::ER_NOZIP: Not a zip archive)

ZipArchive::open() does not throw; it returns true on success or an integer error code, and 19 (ZipArchive::ER_NOZIP) means the file exists but does not contain a valid ZIP structure. In WordPress the same problem appears as 'Incompatible Archive' when installing a plugin or theme.

Also appears as: Incompatible Archive. PCLZIP_ERR_BAD_FORMAT (-10) : Unable to find End of Central Dir Record signature · ZipArchive::open() returned 19 · The package could not be installed. Incompatible Archive. · ZipArchive::open() returned 11 (ER_OPEN: Can't open file)

Common causes

  • The file is empty or truncated because an upload hit upload_max_filesize/post_max_size or a download failed
  • Your code downloaded an HTML error page, redirect or JSON response and saved it as .zip
  • The ZIP is opened before the code that writes it has closed the file handle
  • The file is a different format (tar.gz, RAR, a theme pack containing nested ZIPs)
  • In WordPress, the uploaded ZIP is a full download package from a marketplace rather than the installable plugin ZIP inside it
  • A path mix-up means you are opening a different file than the one you think (relative paths, tmp_name vs name)

How to fix it

  1. Check the return value properly. Compare with === true, and log the integer code when it is not. Code 19 is ER_NOZIP, 11 is ER_OPEN (cannot open), 9 is ER_NOENT (no such file), 5 is ER_READ.
  2. Inspect the file on disk. Log filesize($path) and the first 4 bytes with bin2hex(file_get_contents($path, false, null, 0, 4)). A ZIP starts with 504b0304; 3c21444f or 3c68746d means HTML was saved instead.
  3. Fix the download code. With cURL, set CURLOPT_FOLLOWLOCATION to true, check the HTTP status code and Content-Type, and only write the file on a 200 response.
  4. Close handles before opening. If your script writes the ZIP itself, call fclose() or ZipArchive::close() and clearstatcache() before reopening it.
  5. Check upload limits. For uploads, check $_FILES['file']['error'] first. A value of 1 or 2 means the file exceeded upload_max_filesize or MAX_FILE_SIZE, so the temp file is incomplete.
  6. WordPress: upload the right ZIP. Unzip a marketplace download on your computer first; the installable plugin or theme ZIP is usually inside it. Upload that inner ZIP via Plugins > Add New > Upload.

PHP 8

$zip = new ZipArchive();
$res = $zip->open($path);
if ($res !== true) {
    $head = bin2hex((string) file_get_contents($path, false, null, 0, 4));
    error_log("ZipArchive::open failed: code $res, size " . filesize($path) . ", head $head");
    throw new RuntimeException('Not a valid ZIP archive');
}
$zip->extractTo($targetDir);
$zip->close();

How to stop it happening again

  • Validate the HTTP status and magic bytes (504b0304) before saving a downloaded file as .zip
  • Always check $_FILES[...]['error'] before processing an upload
  • Treat ZipArchive::open() as returning a code, never just a truthy value

Frequently asked questions

Why does if ($zip->open($file)) pass even when it fails?

Because error codes such as 19 are non-zero integers, which PHP treats as truthy. Always compare with === true.

What do the other ZipArchive error codes mean?

Common ones are 9 (ER_NOENT, file not found), 11 (ER_OPEN, cannot open, often permissions), 14 (ER_MEMORY), 19 (ER_NOZIP) and 21 (ER_INCONS, inconsistent archive). They are available as ZipArchive::ER_* constants.

Is 'Incompatible Archive' in WordPress the same problem?

Yes. WordPress falls back to PclZip when ZipArchive fails, and PclZip reports the same missing end-of-central-directory record. Re-download the plugin or upload the correct inner ZIP.