Dateien sicher zum Download ausliefern in PHP – mit readfile() und den richtigen Headern
Ein Download-Link auf eine private Datei ist schnell gefährlich – Pfad-Tricks führen sonst zu fremden Dateien. So lieferst du Downloads sauber aus: geschützt vor „../", speicherschonend und mit korrektem Dateinamen.
Ein häufiger Wunsch: Nutzer sollen eine Datei herunterladen, die nicht frei im Web-Root liegt – eine Rechnung, ein Export, ein gekauftes PDF. Der naive Weg (download.php?file=... und die Datei einfach durchreichen) ist eine klassische Lücke: Mit ?file=../../config.php liest jemand plötzlich deine Geheimnisse. So mache ich es sicher.
Der sichere Handler
<?php
$dir = __DIR__ . '/private-files/';
// 1) NUR den Dateinamen nehmen – basename() wirft jedes "../" weg.
$name = basename((string)($_GET['file'] ?? ''));
$path = $dir . $name;
// 2) Wirklich prüfen, dass die Datei existiert und IM erlaubten Ordner liegt.
$real = realpath($path);
if ($real === false || !str_starts_with($real, realpath($dir) . DIRECTORY_SEPARATOR)) {
http_response_code(404);
exit('Nicht gefunden.');
}
// 3) Header setzen – Typ, Dateiname, Größe.
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $name . '"');
header('Content-Length: ' . filesize($real));
header('X-Content-Type-Options: nosniff');
// 4) Streamen statt in den Speicher laden.
readfile($real);
exit;Warum genau diese Schritte
basename()+realpath()-Check: Der doppelte Schutz gegen *Path Traversal*.basename()entfernt Pfadanteile, und derrealpath()-Vergleich stellt sicher, dass die aufgelöste Datei wirklich in deinem Ordner liegt – nicht per Symlink woanders.Content-Disposition: attachment: Erzwingt den Download-Dialog statt Anzeige im Browser. Willst du z. B. ein PDF *im* Browser zeigen, nimminlinestattattachment.readfile(): Schaufelt die Datei blockweise raus, statt sie komplett in den RAM zu laden – so funktioniert auch ein 500-MB-Download ohne Speicherfehler.X-Content-Type-Options: nosniff: Verhindert, dass der Browser den Inhaltstyp „errät".
Bei sehr großen Dateien lohnt sich vorher noch if (ob_get_level()) ob_end_clean();, damit kein Output-Buffer die Datei zwischenspeichert.
Genau nach diesem Muster liefert mein CMS geschützte Kunden-Downloads aus. Wenn du so etwas brauchst, meld dich über bymw.de.
Quellen
Du brauchst mehr als ein Snippet?
Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.
Projekt anfragen →Verwandte Snippets
Passwörter sicher speichern mit password_hash()
Niemals Passwörter im Klartext oder mit MD5 speichern. Mit password_hash() und password_verify() macht PHP das Richtige von selbst – inklusive Salt und späterem Rehash.
Claude (Anthropic) aus PHP aufrufen – die Messages-API mit cURL
Ein KI-Feature in deine PHP-Website einbauen, ohne SDK: Die Anthropic Messages-API ist ein einziger HTTP-Endpunkt. Hier der komplette, sichere Aufruf per cURL – mit Key aus der Umgebung, nicht im Code.
PDO mit SQLite – sichere Abfragen per Prepared Statement
SQLite ist eine ganze Datenbank in einer Datei – perfekt für kleine Projekte. Mit PDO und Prepared Statements fragst du sie sicher ab, ganz ohne SQL-Injection.