By Greg Nowak. Last updated 2026-07-06.
Shared hosting still creates awkward deployment moments. A site build is ready, FTP is slow, there is no SSH access, and the hosting panel either has no file manager or one that times out on larger uploads. In that situation, uploading one ZIP archive and extracting it on the server can be the least bad option.
The trick is to treat this as a controlled maintenance task, not as a casual shortcut. For most PHP hosts, the right starting point is PHP's built-in ZipArchive. A browser-based unzipper can still help on restricted hosts, but it should be temporary, removed afterwards, and never become the standard release process for a business-critical site.
Use ZipArchive first
ZipArchive is the cleanest default because it is part of PHP's ZIP extension, is documented by PHP, and keeps the deployment helper small enough to review. Put the ZIP and script in a non-public maintenance folder if your host allows it. If not, use an obscure temporary filename, run it once, and delete it immediately.
<?php
$archive = __DIR__ . '/site-release.zip';
$target = __DIR__ . '/release';
if (!class_exists('ZipArchive')) {
exit('ZIP support is not enabled on this host.');
}
if (!is_file($archive)) {
exit('Archive not found.');
}
if (!is_dir($target) && !mkdir($target, 0755, true)) {
exit('Could not create target folder.');
}
$zip = new ZipArchive();
$result = $zip->open($archive);
if ($result !== true) {
exit('Could not open ZIP archive. Error code: ' . $result);
}
for ($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
$unsafe = $name === false
|| $name === ''
|| $name[0] === '/'
|| preg_match('#(^|[\\/])\.\.([\\/]|$)#', $name)
|| preg_match('#^[A-Za-z]:[\\/]#', $name);
if ($unsafe) {
$zip->close();
exit('Unsafe path in archive.');
}
}
umask(0022);
if ($zip->extractTo($target)) {
echo 'Archive extracted to release folder.';
} else {
echo 'Extraction failed.';
}
$zip->close();
The extra path check is deliberate. On a real client host, you want boring failure modes: if the ZIP contains unexpected absolute paths, Windows drive paths, or parent-directory references, stop and inspect the archive locally. The umask(0022) call is also worth keeping because PHP's own manual notes that extracted files and directories otherwise use very broad default permissions unless the current umask restricts them.
Check ZIP support before rewriting the script
If the script fails before opening the archive, the issue may be the host, not your code. With SSH, run:
php -m | grep -i zipWithout SSH, look in the hosting panel's PHP extension list or run a tiny temporary check with class_exists('ZipArchive'). On Linux, PHP must be built with ZIP support. On Windows hosts, current PHP documentation says php_zip.dll must be enabled in php.ini as of PHP 8.2.0.
For an agency or operations team, that distinction matters. If ZIP support is missing, the next action is a hosting change, extension request, or different deployment method. Spending another hour changing syntax will not fix a disabled extension.
| Situation | Best move | Why it matters |
|---|---|---|
| ZIP extension is enabled | Use ZipArchive into a dedicated release folder |
Small, reviewable script with fewer moving parts |
| No SSH, but PHP works | Use a browser helper only for the one task | Useful on restricted hosts, risky if left behind |
| Host runs old or unsupported PHP | Plan a host or PHP upgrade before normalizing the workflow | Deployment fixes should not hide platform risk |
| Uploads happen every week | Create a simple release and rollback process | Repeated ZIP uploads are a process smell, not a strategy |
Use browser-based unzippers carefully
The older recommendation to use ndeet/unzipper still has a place, but the framing should be more cautious now. The project is built for the no-shell scenario: upload unzipper.php, place it near the archive, open it in a browser, choose the archive, and extract it. Its README describes it as handy when you do not have shell access.
That said, its repository also describes the project as beta and says to use it at your own risk. The latest GitHub release shown for the project is version 0.1.1 from January 5, 2018. That does not automatically make it unusable, but it does make it a one-off utility rather than something to leave installed on a live website.
Run the task like a small deployment
Before extracting, confirm that the archive was created from the correct folder. A common mistake is packaging the parent directory and ending up with /release/site-release/index.php instead of /release/index.php. Extract into a staging or release folder first, inspect the result, then switch files into place according to the host's constraints.
After extraction, delete the ZIP, delete the helper script, and keep a copy of the previous working version until the new one is checked. If the site has writable upload folders, cache folders, or local configuration files, make sure the release does not overwrite them blindly.
When to stop doing this manually
Unzipping with PHP is useful for constrained handoffs, emergency fixes, small static exports, and hosts that do not justify a larger build pipeline. It is not a healthy long-term deployment model for a site that changes often or has revenue attached to uptime.
If this task keeps returning, the useful next step is modest: a repeatable package structure, a clear release folder, permission checks, cleanup rules, and a rollback path. Greg can help turn ad hoc shared-hosting uploads into a safer website maintenance workflow without overengineering the setup.
Related on GrN.dk
- Cloudflare Page Rules Debt: The Quiet Way Business Websites Break
- Drupal CMS 2.0 Speeds Marketing Site Rebuilds, but It Is Not Autopilot
- Drush: The Drupal Shell for Practical Site Operations
Need help with this kind of work?
Need a safer deployment handoff? Get in touch with Greg.