# Miten PHP käsittelee tiedostolataukset

> Miten PHP käsittelee tiedostolataukset sisäisesti: multipart/form-data, tilapäistiedostot upload_tmp_dir-hakemistossa, $_FILES-taulukko, rajat ja virhekoodit.

Lähde: https://phpsos.de/fi/php/datei-uploads-intern/  
Tilanne: 25.10.2026  
Palveluntarjoaja: PHP SOS, PHP-hätäapu ja palvelimen tietoturva. Pikaapu WhatsAppilla: https://wa.me/491736494914?text=Hei%20PHP%20SOS%2C%20tarvitsemme%20apua%20PHP-palvelimemme%20kanssa.%20Yritys%3A%20%E2%80%A6%20Verkkosivusto%3A%20%E2%80%A6

**Lyhyesti:** Kun selain lataa tiedoston, se lähettää POST-pyynnön muodossa `multipart/form-data`, jossa lomakekentät ja tiedostojen sisältö ovat peräkkäisinä erillisinä osina. PHP pilkkoo pyynnön itse, kirjoittaa jokaisen tiedosto-osan tilapäistiedostoon hakemistoon `upload_tmp_dir`, kirjaa tiedot taulukkoon `$_FILES` ja käynnistää komentosarjanne vasta sen jälkeen. Komentosarja siirtää tiedoston funktiolla `move_uploaded_file()`; kaiken, mitä ei siirretä, PHP poistaa pyynnön päättyessä.

## Tiedoston matka selaimesta komentosarjaan

Päättäjälle on tärkeintä ymmärtää yksi asia: lataus ei ole pelkästään sovelluksen toimi. Suuri osa työstä tehdään PHP-tulkissa jo ennen kuin CMS- tai verkkokauppakoodi on lainkaan mukana. Siksi tämän PHP:n osan virheet koskevat jokaista verkkosivustoa, olipa siinä latauslomake tai ei.

Kulku pähkinänkuoressa:

1. Lomake, jossa on `method="POST"` ja `enctype="multipart/form-data"`, lähetetään.
2. Verkkopalvelin ottaa pyynnön vastaan ja välittää sen PHP:lle ajotavasta riippuen PHP-FPM:n, mod_php:n tai muun rajapinnan kautta.
3. PHP tarkistaa rajat, kuten `post_max_size`, ja pilkkoo sisällön erotinmerkkijonon, niin sanotun boundaryn, perusteella.
4. Tavalliset kentät päätyvät taulukkoon `$_POST`, tiedosto-osat kirjoitetaan tilapäistiedostoihin ja kuvataan taulukossa `$_FILES`.
5. Vasta nyt komentosarja käynnistyy, tarkistaa tiedoston ja siirtää sen kohteeseensa.
6. Pyynnön päättyessä PHP poistaa kaikki tilapäistiedostot, joita ei ole siirretty.

## multipart/form-data: latauspyynnön rakenne

Latauspyyntö on pelkkää tekstiä, johon on upotettu tiedostodataa. Otsake `Content-Type` kertoo boundaryn, ja sisällössä tämä merkkijono erottaa osat toisistaan. Jokaisella osalla on omat otsakerivinsä, joissa on kentän nimi ja tarvittaessa tiedostonimi. Voimakkaasti lyhennetty, vaaraton esimerkki:

```http
POST /bewerbung HTTP/1.1
Content-Type: multipart/form-data; boundary=----trenner42

------trenner42
Content-Disposition: form-data; name="vorname"

Erika
------trenner42
Content-Disposition: form-data; name="lebenslauf"; filename="lebenslauf.pdf"
Content-Type: application/pdf

%PDF-1.7 ... Dateiinhalt ...
------trenner42--
```

Kaikki tämän tekstin tiedot tulevat lähettäjältä, myös tiedostonimi ja osan `Content-Type`. PHP ottaa ne `$_FILES`-taulukkoon mutta ei tarkista niiden uskottavuutta.

## $_FILES-taulukko

Kentälle nimeltä `lebenslauf` taulukko `$_FILES['lebenslauf']` sisältää PHP-käsikirjan mukaan nämä avaimet:

| Avain | Sisältö | Luotettava? |
|---|---|---|
| `name` | tiedoston alkuperäinen nimi lähettäjällä | ei |
| `full_path` | selaimen ilmoittama polku, versiosta PHP 8.1.0 lähtien | ei |
| `type` | selaimen ilmoittama MIME-tyyppi, jota PHP ei tarkista | ei |
| `size` | koko tavuina | PHP:n määrittämä |
| `tmp_name` | tilapäistiedoston polku palvelimella | PHP:n antama |
| `error` | latauskäsittelyn virhekoodi | PHP:n asettama |

Jos kentän nimessä on hakasulkeet, esimerkiksi `bilder[]`, jokainen avain sisältää taulukon, jossa on yksi alkio tiedostoa kohti. Sovellus käy silloin läpi esimerkiksi `$_FILES['bilder']['error']` ja hakee `tmp_name`- ja `name`-arvot samalla indeksillä.

Lomake voi lisäksi lähettää tiedostokentän edellä piilokentän `MAX_FILE_SIZE`. Käsikirjan mukaan se on vain selaimelle tarkoitettu vihje, jonka voi helposti kiertää. Voitte luottaa yksinomaan niihin rajoihin, jotka palvelin pakottaa: PHP:ssä, verkkopalvelimessa ja itse sovelluksessa.

## Rajat: latausten php.ini-direktiivit

Nämä direktiivit ohjaavat latausten käsittelyä. Oletusarvot ovat PHP-käsikirjasta, ja ne pätevät, jos mikään php.ini ei ylikirjoita niitä.

| Direktiivi | Oletus | Muutettavissa | 
|---|---|---|
| `file_uploads` | `1` | vain järjestelmäkonfiguraatiossa |
| `upload_tmp_dir` | tyhjä (järjestelmän tilapäishakemisto) | vain järjestelmäkonfiguraatiossa |
| `upload_max_filesize` | `2M` | php.ini, pooli, hakemistokonfiguraatio |
| `post_max_size` | `8M` | php.ini, pooli, hakemistokonfiguraatio |
| `max_file_uploads` | `20` | php.ini, pooli, hakemistokonfiguraatio |
| `enable_post_data_reading` | `1` | php.ini, pooli, hakemistokonfiguraatio |

Asetuksen `post_max_size` on oltava suurempi kuin `upload_max_filesize`, ja `memory_limit` kannattaa käsikirjan mukaan yleensä asettaa suuremmaksi kuin `post_max_size`. Jos `upload_tmp_dir` ei ole kirjoitettavissa, PHP siirtyy käyttämään järjestelmän tilapäishakemistoa. Kun käytössä on `open_basedir`, tilapäishakemiston on oltava sallittu, muuten lataukset epäonnistuvat. Käytännön arvoja ja niiden seurauksia käsittelee artikkeli [post_max_size ja upload_max_filesize järkevästi rajattuina](https://phpsos.de/fi/blog/post-max-size-upload-limits/).

## Virhekoodien oikea tulkinta

Kenttä `error` kertoo, mitä käsittelyssä tapahtui. Vakiot ulottuvat arvosta `UPLOAD_ERR_OK` (0) arvojen `UPLOAD_ERR_INI_SIZE` (1), `UPLOAD_ERR_FORM_SIZE` (2), `UPLOAD_ERR_PARTIAL` (3) ja `UPLOAD_ERR_NO_FILE` (4) kautta arvoihin `UPLOAD_ERR_NO_TMP_DIR` (6), `UPLOAD_ERR_CANT_WRITE` (7) ja `UPLOAD_ERR_EXTENSION` (8).

```php
<?php
declare(strict_types=1);

$datei = $_FILES['lebenslauf'] ?? null;
if ($datei === null) {
    exit('Keine Upload-Daten, eventuell wurde post_max_size überschritten.');
}

$meldung = match ($datei['error']) {
    UPLOAD_ERR_OK => null,
    UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'Datei zu groß.',
    UPLOAD_ERR_NO_FILE => 'Keine Datei gewählt.',
    default => 'Upload fehlgeschlagen.',
};

if ($meldung === null && is_uploaded_file($datei['tmp_name'])) {
    $ziel = '/srv/app/uploads/' . bin2hex(random_bytes(16)) . '.pdf';
    move_uploaded_file($datei['tmp_name'], $ziel);
}
```

Esimerkki näyttää vain mekaniikan. Tiedostotyypin tarkistusta, tallennusta verkkojuuren ulkopuolelle ja komentosarjojen suorituksen estämistä käsittelee opas [Tiedostolataukset PHP:ssä turvallisesti](https://phpsos.de/fi/ratgeber/datei-upload-sicher/).

## Lataukset ja PHP2Shell: mitä väliaikaiset toimenpiteet tarkoittavat

PHP2Shell-haavoittuvuudet CVE-2026-109618 ja CVE-2026-109619 koskevat pyyntöä ympäröivää käsittelyä PHP-tulkissa. Siksi php.net mainitsee väliaikaisina toimenpiteinä muun muassa arvon `post_max_size = 8M` (ilman latauksia) tai `32M` (latausten kanssa), sitä pienemmän `upload_max_filesize`-arvon sekä asetuksen `session.upload_progress.enabled=0`. Nämä arvot kuuluvat php.ini-tiedostoon, FPM-poolin tai palvelimen konfiguraatioon, ja sen jälkeen kaikkien worker-prosessien on käynnistyttävä uudelleen.

Asiantuntijat ehdottavat lisäksi, että `multipart/form-data`-pyynnöt estetään välityspalvelimella tai WAF:ssa muualla kuin aidoilla latauspoluilla. Se on epävirallinen toimenpide, ja riskinä on lomakkeiden rikkoutuminen; punnintaa käsittelee artikkeli [Latauspyyntöjen suodatus reunalla](https://phpsos.de/fi/blog/upload-anfragen-am-rand-filtern/). Mikään näistä toimenpiteistä ei korvaa päivitystä versioon PHP 8.5.12, 8.4.27, 8.3.36 tai 8.2.35.

## Miten PHP SOS auttaa konkreettisesti?

- Selvitämme, mitkä verkkosivustoistanne todella tarvitsevat latauksia ja millä poluilla.
- Asetamme rajat ja väliaikaiset toimenpiteet poolikohtaisesti, käynnistämme kaikki worker-prosessit uudelleen ja testaamme lomakkeet.
- Tarkistamme latauskansioista suoritettavat tiedostot ja estämme komentosarjojen suorituksen [palvelimen koventamisen](https://phpsos.de/fi/leistungen/server-haertung/) puitteissa.
- Asennamme PHP-päivityksen ja dokumentoimme korjaustilanteen sivustoittain.


## Usein kysytyt kysymykset

**Minne PHP tallentaa ladatut tiedostot ensin?**  
Tilapäishakemistoon. Oletuksena se on järjestelmän tilapäishakemisto, ja sitä voi muuttaa asetuksella upload_tmp_dir. Jos komentosarja ei siirrä tiedostoa, PHP poistaa sen pyynnön päättyessä.

**Voinko luottaa $_FILES-taulukon type-kenttään?**  
Et voi. MIME-tyypin ilmoittaa selain, eikä PHP käsikirjan mukaan tarkista sitä. Myös name ja full_path tulevat lähettäjältä. Todellinen tiedostotyyppi selvitetään palvelinpuolella tiedoston sisällön perusteella.

**Miksi $_FILES on tyhjä, vaikka tiedosto lähetettiin?**  
Useimmiten koko pyyntö oli suurempi kuin post_max_size. Silloin PHP tyhjentää $_POST- ja $_FILES-taulukon kokonaan, eikä taulukossa ole virhekoodia. Muita syitä ovat lomakkeesta puuttuva enctype tai pois päältä kytketty file_uploads.

**Auttaako lataustenkielto sovelluksessa PHP2Shelliä vastaan?**  
Ei auta. Pyyntötietojen käsittely tapahtuu PHP:ssä ennen kuin sovelluskoodi käynnistyy. Tehokkaita ovat PHP-päivitys ja kaikkien worker-prosessien uudelleenkäynnistys; tiukemmat rajat ovat vain väliaikainen toimenpide.

**Mikä on ero upload_max_filesize- ja post_max_size-asetusten välillä?**  
upload_max_filesize rajoittaa jokaista yksittäistä tiedostoa, post_max_size koko pyynnön sisältöä kaikkine tiedostoineen ja kenttineen. Siksi post_max_size:n on oltava suurempi kuin upload_max_filesize.

## Lähteet

- [PHP-käsikirja: lataukset POST-menetelmällä](https://www.php.net/manual/de/features.file-upload.post-method.php)
- [PHP-käsikirja: latausten virheilmoitukset](https://www.php.net/manual/de/features.file-upload.errors.php)
- [PHP-käsikirja: php.ini-direktiivit](https://www.php.net/manual/de/ini.core.php)
- [php.net: tietoturvailmoitukset](https://www.php.net/)
