By Greg Nowak. Updated 7 September 2026.
Uploading one ZIP archive can save time when a website release contains thousands of small files. If your shared hosting has no SSH access and its file manager cannot extract archives, PHP can handle the job.
The extraction itself is only one step. For a business website, the useful outcome is a complete release, a checked customer journey and a clear way back if something fails. Here is how to organise that work without overwriting the live site halfway through an update.
Choose the simplest available route
Check the hosting panel before adding a script. Its extraction feature may be sufficient, provided you can choose a separate destination. For an agency handover, record who uploads the package, who activates it and who checks the result.
| Hosting situation | Practical approach | Before proceeding |
|---|---|---|
| File manager supports ZIP extraction | Use the hosting panel | Select a fresh release directory |
| No SSH or panel extraction; PHP ZIP is available | Use a temporary PHP helper | Protect access and staging files |
| PHP ZIP is missing | Ask the host to enable it or extract the package | Confirm support for the site's PHP runtime |
| Large archives or repeated timeouts | Ask for a host-managed or command-line operation | Check disk quota and execution limits |
Check PHP, space and access first
With shell access, check ZIP support using:
php -m | grep -i zipThe command-line runtime may differ from the website's PHP configuration. For a browser helper, check the hosting panel or temporarily run var_dump(class_exists('ZipArchive')); behind access protection, then delete it. The PHP installation documentation explains the extension requirements; shared-hosting changes usually belong with the host.
Use a supported PHP branch with current patches. On 7 September 2026, PHP 8.2–8.5 remain supported; 8.2 and 8.3 receive security fixes only. PHP 8.2 support ends on 31 December 2026, according to the official support schedule.
Allow space for the ZIP, expanded release and previous release, plus working headroom. Check the account's file-count quota too. A small compressed file can contain many entries.
Before uploading the helper, protect its URL with hosting-panel authentication over HTTPS or an appropriate IP restriction. An obscure filename is insufficient. Keep the archive and staging directory outside the public document root where possible; otherwise block web access to both. Filesystem permissions alone do not stop the web server serving files it can read.
A bounded ZipArchive helper for trusted releases
This example is for a team-produced, reviewed archive on Linux shared hosting. Place it in the protected maintenance workspace described above. It uses fixed paths, rejects questionable entry names and requires a new destination. Adjust the example limits to the expected package.
<?php
$archive = __DIR__ . '/site-release.zip';
$target = __DIR__ . '/release-2026-09-07';
$maxEntries = 2000;
$maxBytes = 200 * 1024 * 1024; // 200 MiB, declared size
if (!class_exists('ZipArchive')) {
exit('ZIP support is unavailable.');
}
if (!is_file($archive)) {
exit('Archive not found.');
}
if (file_exists($target) || is_link($target)) {
exit('Use a new release directory.');
}
$zip = new ZipArchive();
$result = $zip->open(
$archive,
ZipArchive::RDONLY | ZipArchive::CHECKCONS
);
if ($result !== true) {
exit('Could not open archive. Error code: ' . $result);
}
$oldUmask = umask(0077);
try {
if ($zip->numFiles > $maxEntries) {
throw new RuntimeException('Too many archive entries.');
}
$total = 0;
for ($i = 0; $i < $zip->numFiles; $i++) {
$stat = $zip->statIndex($i);
if ($stat === false) {
throw new RuntimeException('Cannot inspect entry.');
}
$path = $stat['name'];
if ($path === '' || $path[0] === '/'
|| str_contains($path, '\\')
|| str_contains($path, ':')
|| preg_match('/[\x00-\x1F\x7F]/', $path)
|| preg_match('#(^|/)\.{1,2}(/|$)#', $path)) {
throw new RuntimeException('Unsupported archive path.');
}
$size = $stat['size'];
if (!is_int($size) || $size < 0
|| $size > $maxBytes - $total) {
throw new RuntimeException('Declared size exceeds limit.');
}
$total += $size;
}
if (!mkdir($target, 0700)) {
throw new RuntimeException('Cannot create release directory.');
}
if (!$zip->extractTo($target)) {
throw new RuntimeException('Extraction failed; inspect partial release.');
}
echo 'Extracted. Validate before activation.';
} catch (RuntimeException $e) {
http_response_code(500);
echo htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
} finally {
umask($oldUmask);
$zip->close();
}
The strict !== true check matters because ZipArchive::open() can return an integer error code. Consistency checking does not establish that the contents are trustworthy.
The size check is preflight validation, not a hard extraction quota. statIndex() supplies archive metadata; this helper does not count bytes as they are written. It is unsuitable for processing arbitrary customer uploads or defending against hostile archives.
The restrictive umask keeps staging permissions narrow. PHP documents that extractTo() does not restore original permissions. Before activation, apply the host's required file and directory permissions, including any executable scripts. Avoid blanket chmod 777 fixes.
Validate the release before switching traffic
Check the directory layout: an extra parent folder can put index.php one level below where the site expects it. Confirm dependencies, environment configuration and writable directories. Keep uploads, secrets and generated content outside the replaceable package wherever practical.
Agree the activation method before starting. Some hosts allow a document-root change; others require a maintenance window and directory changes. Do not assume switching will be instantaneous. Keep the previous release and test the homepage, a key enquiry or purchase journey, and the error log after activation.
If the release changes the database, restoring old files may not restore the application. Plan compatible migrations or a tested database recovery procedure, accounting for orders and submissions received since the backup.
Close the maintenance job properly
A timeout or failed extraction can leave a partial directory. Inspect it, remove the failed release and retry into a fresh destination. Delete the temporary helper and uploaded ZIP after the job; record the release, checks and rollback location.
If these deployments depend on one person's memory, Greg can help document and coordinate a repeatable release workflow that fits your hosting, team and budget.
Related on GrN.dk
- Debugging WordPress on a Live Site: A Safer Workflow
- PHP Only on the Front Page: Safer Checks for Live Sites
- A Voice Agent Is Only Ready When the Human Handoff Works
Need help with this kind of work?
Discuss your website deployment workflow Get in touch with Greg.