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

Seneste artikler

Få tilbudsmails fra Outlook ind i CRM med AI. Afklar felter, dubletter og sælgeransvar, og mål i en pilot, om løsningen sparer tid og får flere forespørgsler fulgt op.

Gør månedsrapporten lettere at gentage med AI til Excel, Python og faste kontroller. Få styr på import, fejllister og afstemning, så tallene kan efterprøves.

Få godkendte timer fra Clockify med på fakturakladder i Dinero. AI skriver ydelsesbeskrivelser, mens faste regler styrer beløb, godkendelse og genkørsler.

Lad AI kundeservice hente ordrestatus fra Shopify og Shipmondo. Se, hvordan I afgrænser opgaven, kontrollerer kundens identitet og sender usikre sager videre.

Når kunden beder om indsigt, kan AI hjælpe med at samle materiale fra WordPress og Microsoft 365. Se, hvordan I holder styr på datakilder, ansvar og kontrol.

Når en ingrediens ændres, kan menukortet kræve rettelser, selv om opskriftsdatoen står stille. Sådan kan AI hjælpe med gennemgang og godkendelse i WordPress.

Når checkout fejler, skal driftspartneren have noget konkret at arbejde med. Se, hvordan AI, dmesg og journalctl kan samle sporene i en brugbar driftssag.

Brug oktober til at afprøve daglige AI-forslag til genbestilling før Black Friday. Få styr på Shopify-data, leveringstid og budget, før forslagene bliver til indkøb.

Jeg lærte serverdrift ved at ødelægge mine egne servere. Jeg søger en, der vil stå ved siden af mig, mens jeg gør det, og så gøre det selv ugen efter.

Jeg er god til at bygge og dårlig til at ringe. Her er, hvem jeg vil have ved siden af mig, hvad der er lettest at sælge, og hvordan vi deler det.