# Sådan håndterer PHP filuploads

> Sådan håndterer PHP filuploads internt: multipart/form-data, midlertidige filer i upload_tmp_dir, $_FILES-arrayet, grænseværdier og fejlkoder forklaret.

Kilde: https://phpsos.de/da/php/datei-uploads-intern/  
Status: 25.10.2026  
Udbyder: PHP SOS, PHP-nødhjælp og serversikkerhed. Akut hjælp på WhatsApp: https://wa.me/491736494914?text=Hej%20PHP%20SOS%2C%20vi%20har%20brug%20for%20hj%C3%A6lp%20med%20vores%20PHP-server.%20Firma%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**Kort sagt:** Når en browser uploader en fil, sender den en POST-forespørgsel i formatet `multipart/form-data`, hvor formularfelter og filindhold står som enkelte dele efter hinanden. PHP opdeler selv denne forespørgsel, skriver hver fildel til en midlertidig fil i `upload_tmp_dir`, registrerer oplysningerne i `$_FILES` og starter først derefter jeres script. Scriptet flytter filen med `move_uploaded_file()`; alt, der ikke flyttes, sletter PHP ved afslutningen af forespørgslen.

## Filens vej fra browseren til scriptet

For beslutningstagere er én ting vigtig: Et upload er ikke en proces, der alene hører til applikationen. En stor del af arbejdet sker i PHP-fortolkeren, før koden i CMS eller shop overhovedet er involveret. Derfor berører fejl i denne del af PHP ethvert website, uanset om det har en uploadformular eller ej.

Forløbet i overblik:

1. Formularen med `method="POST"` og `enctype="multipart/form-data"` sendes af sted.
2. Webserveren modtager forespørgslen og giver den videre til PHP, alt efter driftsform via PHP-FPM, mod_php eller en anden grænseflade.
3. PHP kontrollerer grænseværdier som `post_max_size` og opdeler indholdet ved hjælp af en adskillelsesstreng, den såkaldte boundary.
4. Almindelige felter havner i `$_POST`, fildele skrives til midlertidige filer og beskrives i `$_FILES`.
5. Først nu starter scriptet, kontrollerer filen og flytter den til dens bestemmelsessted.
6. Ved afslutningen af forespørgslen sletter PHP alle midlertidige filer, der ikke blev flyttet.

## multipart/form-data: opbygningen af en uploadforespørgsel

En uploadforespørgsel er ren tekst med indlejrede fildata. Headeren `Content-Type` angiver boundary, og i indholdet adskiller denne tegnfølge de enkelte dele. Hver del har egne headerlinjer med feltnavn og eventuelt filnavn. Et stærkt forkortet, harmløst eksempel:

```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--
```

Alle oplysninger i denne tekst stammer fra afsenderen, også delens filnavn og `Content-Type`. PHP overtager dem i `$_FILES`, men kontrollerer ikke, om de er plausible.

## $_FILES-arrayet

For et felt med navnet `lebenslauf` indeholder `$_FILES['lebenslauf']` ifølge PHP-manualen disse nøgler:

| Nøgle | Indhold | Pålidelig? |
|---|---|---|
| `name` | oprindeligt filnavn hos afsenderen | nej |
| `full_path` | sti oplyst af browseren, siden PHP 8.1.0 | nej |
| `type` | MIME-type ifølge browseren, ikke kontrolleret af PHP | nej |
| `size` | størrelse i bytes | fastlagt af PHP |
| `tmp_name` | stien til den midlertidige fil på serveren | tildelt af PHP |
| `error` | fejlkode fra uploadbehandlingen | sat af PHP |

Ved felter med kantede parenteser i navnet, f.eks. `bilder[]`, indeholder hver nøgle et array med én post pr. fil. Applikationen gennemløber så for eksempel `$_FILES['bilder']['error']` og tilgår `tmp_name` og `name` via det samme indeks.

En formular kan desuden sende et skjult felt `MAX_FILE_SIZE` før filfeltet. Ifølge manualen er det kun et fingerpeg til browseren, som let kan omgås. I kan udelukkende forlade jer på de grænser, som serveren håndhæver: i PHP, i webserveren og i selve applikationen.

## Grænseværdier: php.ini-direktiverne for uploads

Disse direktiver styrer uploadbehandlingen. Standardværdierne stammer fra PHP-manualen og gælder, hvis ingen php.ini overskriver dem.

| Direktiv | Standard | Kan ændres i |
|---|---|---|
| `file_uploads` | `1` | kun systemkonfiguration |
| `upload_tmp_dir` | tom (system-temp) | kun systemkonfiguration |
| `upload_max_filesize` | `2M` | php.ini, pool, mappekonfiguration |
| `post_max_size` | `8M` | php.ini, pool, mappekonfiguration |
| `max_file_uploads` | `20` | php.ini, pool, mappekonfiguration |
| `enable_post_data_reading` | `1` | php.ini, pool, mappekonfiguration |

`post_max_size` skal være større end `upload_max_filesize`, og `memory_limit` bør ifølge manualen som regel være større end `post_max_size`. Er `upload_tmp_dir` ikke skrivbar, falder PHP tilbage til systemets temp-mappe. Med `open_basedir` skal temp-mappen være tilladt, ellers mislykkes uploads. Praktiske værdier og deres følger beskriver indlægget [Fornuftig begrænsning af post_max_size og upload_max_filesize](https://phpsos.de/da/blog/post-max-size-upload-limits/).

## Fejlkoder: sådan vurderer I dem rigtigt

Feltet `error` fortæller, hvad der skete under behandlingen. Konstanterne spænder fra `UPLOAD_ERR_OK` (0) over `UPLOAD_ERR_INI_SIZE` (1), `UPLOAD_ERR_FORM_SIZE` (2), `UPLOAD_ERR_PARTIAL` (3) og `UPLOAD_ERR_NO_FILE` (4) til `UPLOAD_ERR_NO_TMP_DIR` (6), `UPLOAD_ERR_CANT_WRITE` (7) og `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);
}
```

Eksemplet viser kun mekanikken. Kontrol af filtypen, placering uden for webroot og spærring af scriptudførelse beskrives i guiden [Implementér filuploads i PHP sikkert](https://phpsos.de/da/ratgeber/datei-upload-sicher/).

## Uploads og PHP2Shell: Hvad de midlertidige foranstaltninger betyder

PHP2Shell-sårbarhederne CVE-2026-109618 og CVE-2026-109619 angår behandlingen omkring forespørgslen i PHP-fortolkeren. Derfor nævner php.net som midlertidige foranstaltninger blandt andet `post_max_size = 8M` (uden uploads) eller `32M` (med uploads), `upload_max_filesize` derunder, samt `session.upload_progress.enabled=0`. Disse værdier hører hjemme i php.ini, FPM-pool eller serverkonfiguration, derefter skal alle workers genstarte.

Fagforfattere foreslår desuden at blokere `multipart/form-data`-forespørgsler uden for ægte uploadstier ved proxyen eller i WAF'en. Det er en uofficiel foranstaltning med risiko for, at formularer holder op med at virke; afvejningen beskriver indlægget [Filtrér uploadforespørgsler i kanten](https://phpsos.de/da/blog/upload-anfragen-am-rand-filtern/). Ingen af disse foranstaltninger erstatter opdateringen til PHP 8.5.12, 8.4.27, 8.3.36 eller 8.2.35.

## Hvordan hjælper PHP SOS konkret?

- Vi finder ud af, hvilke af jeres websites der reelt har brug for uploads, og på hvilke stier.
- Vi indstiller grænseværdierne og de midlertidige foranstaltninger pr. pool, genstarter alle workers og tester formularerne.
- Vi kontrollerer upload-mapper for eksekverbare filer og spærrer scriptudførelse som led i [serverhærdningen](https://phpsos.de/da/leistungen/server-haertung/).
- Vi indspiller PHP-opdateringen og dokumenterer patch-status pr. website.


## Ofte stillede spørgsmål

**Hvor gemmer PHP uploadede filer først?**  
I en midlertidig mappe. Som standard er det systemets midlertidige mappe; med upload_tmp_dir kan den ændres. Flyttes filen ikke i scriptet, sletter PHP den ved afslutningen af forespørgslen.

**Kan jeg stole på feltet type i $_FILES?**  
Nej. MIME-typen oplyses af browseren, og ifølge manualen kontrollerer PHP den ikke. Også name og full_path stammer fra afsenderen. Den faktiske filtype afgør I på serversiden ud fra indholdet.

**Hvorfor er $_FILES tom, selv om en fil blev sendt?**  
Oftest var hele forespørgslen større end post_max_size. Så tømmer PHP $_POST og $_FILES fuldstændigt, uden en fejlkode i arrayet. Andre årsager er et manglende enctype i formularen eller slåede file_uploads fra.

**Hjælper det mod PHP2Shell at forbyde uploads i applikationen?**  
Nej. Behandlingen af forespørgselsdata sker i PHP, før applikationskoden kører. Effektive er PHP-opdateringen og genstart af alle workers; snævrere grænseværdier er kun en midlertidig foranstaltning.

**Hvad er forskellen på upload_max_filesize og post_max_size?**  
upload_max_filesize begrænser hver enkelt fil, post_max_size hele forespørgslens indhold inklusive alle filer og felter. post_max_size skal derfor være større end upload_max_filesize.

## Kilder

- [PHP-manualen: Uploads med POST](https://www.php.net/manual/de/features.file-upload.post-method.php)
- [PHP-manualen: Fejlmeddelelser ved uploads](https://www.php.net/manual/de/features.file-upload.errors.php)
- [PHP-manualen: php.ini-direktiver](https://www.php.net/manual/de/ini.core.php)
- [php.net: Sikkerhedsmeddelelser](https://www.php.net/)
