Unzip with PHP on Shared Hosting: A Safer Way to Extract ZIP Files

Illustrated infographic summarizing: Unzip with PHP on Shared Hosting: A Safer Way to Extract ZIP Files

By Greg Nowak. Updated 1 November 2026.

Uploading one ZIP can make a website update much easier when the alternative is transferring thousands of individual files. If your shared hosting has no SSH access and its file manager cannot extract archives, PHP’s ZipArchive can do the job.

For a business website, extraction belongs inside a release plan: prepare the files separately, check the important customer journeys, and agree how to recover. Keep the live directory intact while you prepare its replacement. That gives the team a useful stopping point if anything looks wrong.

Choose the extraction method your host supports

Check the hosting panel first. A built-in extraction tool may cover the job without adding a maintenance script. Whichever route you choose, extract into a fresh directory.

Hosting situation Recommended route Check first
File manager can extract ZIP files Use the hosting panel You can select a separate destination
No panel extraction or SSH; PHP ZIP is available Use a temporary, protected PHP helper Private workspace and sufficient account quota
PHP ZIP is unavailable Ask the host to enable it or extract the package The website’s actual PHP configuration
Large package or repeated timeouts Arrange extraction with the host Execution limits, disk space and file-count limits
Choose the route before uploading the release package.

Prepare the account before running PHP

If shell access is available, check the command-line runtime with:

php -m | grep -i zip

The website may use a different PHP configuration. Check its hosting settings or temporarily run var_dump(class_exists('ZipArchive')); behind access protection, then delete the diagnostic. If support is missing, use the host’s extension controls or contact support; the PHP installation documentation explains the requirements.

Use a supported, patched PHP version compatible with the application. On 1 November 2026, PHP 8.2–8.5 remain supported, but PHP 8.2 reaches end of security support on 31 December 2026. Schedule its replacement using the official support timetable.

Allow disk space for the ZIP, expanded files and previous release, plus working headroom. Check file-count quotas too. Protect the helper with hosting-level authentication over HTTPS. An obscure filename provides no meaningful access control.

Keep the archive and extraction directory outside the public document root. If that is impossible, arrange and verify server rules blocking web access to both before uploading. Restrictive filesystem permissions alone may still allow the web server to serve their contents.

Use a bounded helper for a reviewed release

This example is for a team-produced, reviewed package on Linux hosting. Hosting-level access protection is a prerequisite. Replace the workspace path with your private directory and set limits appropriate to the known package; 2,000 entries and 200 MiB are example limits.

<?php
$workspace = '/home/ACCOUNT/private-deploy';
$archive = $workspace . '/site-release.zip';
$target = $workspace . '/release-2026-11-01';
$maxEntries = 2000;
$maxBytes = 200 * 1024 * 1024;

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 < 1 || $zip->numFiles > $maxEntries) {
        throw new RuntimeException('Unexpected archive entry count.');
    }
    $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, chr(92))
            || str_contains($path, ':')
            || preg_match('/[[:cntrl:]]/', $path)
            || array_intersect(explode('/', $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 success check matters: ZipArchive::open() can return an integer error code. Consistency checking does not establish that the contents are trustworthy.

The size limit checks declared metadata. statIndex() supplies entry details; this helper does not enforce a byte limit during extraction. Use a separately designed upload-processing system for untrusted customer archives.

The restrictive umask narrows staging permissions. PHP’s extractTo() documentation confirms that original permissions are not restored. Before activation, apply the host’s required permissions, including executable permissions where needed. Avoid blanket chmod 777 fixes.

Check the release before switching the live site

Confirm the directory layout: an extra parent folder can leave index.php one level too deep. Check dependencies, environment configuration and writable directories. Preserve uploads, secrets and generated content when replacing application files.

Agree who activates the release, who checks it and what triggers rollback. Confirm whether the host supports changing the document root or requires directory changes during a maintenance window. Keep the previous release available.

After activation, test the homepage and a complete enquiry, booking or purchase journey. Check the error log and verify that submissions reach their destination. A page loading successfully is only part of the acceptance check.

Database changes need their own recovery plan. Restoring old files may leave them incompatible with the updated database. Account for orders and enquiries received since any backup before restoring data.

Finish with a clean handover

A timeout can leave a partial release. Inspect and remove that directory, investigate the limit, then retry into a fresh destination. Delete the helper and uploaded ZIP after completion. Record the release identifier, checks performed and rollback location.

If updates rely on one person remembering every step, Greg can help coordinate a repeatable deployment workflow with your host, developers and agency team, including clear ownership and practical acceptance checks.

Related on GrN.dk

Need help with this kind of work?

Discuss your website release workflow with Greg Get in touch with Greg.

Sources

Latest articles

Before tuning your AI chatbot, check whether your PDFs supply readable text, reliable reading order, usable tables, and passages you can trace to the page.

Bring quote request emails from Outlook into your CRM with AI. Define fields, duplicate checks and sales ownership, then use a pilot to measure whether the solution saves time and helps more enquiries get followed up.

Plan your PHP upgrade around checkout, enquiries and daily operations, with practical checks for compatibility, staging, deployment and rollback before support ends.

Claude Sonnet 4.5 has a tentative November 30 retirement date. Find affected workflows, check compatibility and test a supported replacement before switching.

Before closing an Apache patch ticket, check what is running, which fixes the package contains, and whether the updated service passes configuration and application checks.

Make monthly reporting easier to repeat with AI for Excel, Python and consistent checks. Get imports, error lists and reconciliation under control so the numbers can be verified.

A supplier PDF can influence more than an AI summary. Follow its path to a CRM update and see where validation, tool permissions and destination controls matter.

Check whether AI shopping channels show the right product, variant, price and availability, then trace discrepancies from your catalog through to checkout.

Add approved hours from Clockify to draft invoices in Dinero. AI writes service descriptions, while fixed rules govern amounts, approvals and reruns.

A blanket AI restriction can limit search discovery. Compare Google and Bing controls, agree content permissions, and check what your CMS actually delivers.