PHP Timeout umgehen — Große Datenbanken sicher exportieren
Du klickst auf „Export“ und nach 30 Sekunden: Abbruch. Weiße Seite. Timeout. Das ist das Problem schlechthin auf Shared Hosting. PHP hat ein Zeitlimit, und wenn dein Datenbank-Export länger braucht, wird der Prozess gnadenlos abgewürgt. Hier sind vier Wege, das Problem zu lösen — vom schnellen Workaround bis zur sauberen Dauerlösung.
Das Problem verstanden
PHP hat eine eingebaute Sicherheitsbremse: max_execution_time. Die ist auf den meisten Servern auf 30 Sekunden eingestellt. Manche Hoster erlauben 60 oder 120 Sekunden, aber das Prinzip bleibt: Wenn dein Script länger läuft, wird es beendet.
Für eine kleine WordPress-Datenbank mit 5 MB ist das kein Problem. Aber sobald du einen Shop mit 50.000 Produkten, ein Forum mit Jahren an Beiträgen oder eine Datenbank mit 200+ MB exportieren willst, reicht die Zeit nicht.
Den aktuellen Wert kannst du mit einer einfachen PHP-Datei prüfen:
<?php
echo "max_execution_time: " . ini_get('max_execution_time') . " Sekunden\n";
echo "memory_limit: " . ini_get('memory_limit') . "\n";
echo "upload_max_filesize: " . ini_get('upload_max_filesize') . "\n";
echo "post_max_size: " . ini_get('post_max_size') . "\n";
?>
Lösung 1: set_time_limit() im Script
Die einfachste Methode — wenn der Hoster es erlaubt. Du setzt das Zeitlimit direkt im PHP-Script hoch oder auf 0 (unbegrenzt):
<?php
// Zeitlimit auf 300 Sekunden (5 Minuten) setzen
set_time_limit(300);
// Oder: Kein Limit
set_time_limit(0);
// Dein Export-Code hier...
?>
Wann funktioniert das?
Auf vielen Hostern ist set_time_limit() erlaubt. Auf Shared Hosting wird es allerdings häufig blockiert — dann hat der Aufruf einfach keinen Effekt.
Prüfen ob es funktioniert
<?php
set_time_limit(0);
$new_limit = ini_get('max_execution_time');
echo "Neues Limit: $new_limit Sekunden\n";
if ($new_limit == 0) {
echo "set_time_limit funktioniert!";
} else {
echo "set_time_limit wird vom Hoster blockiert.";
}
?>
Vorteile
- Einfachste Lösung — eine Zeile Code
- Kein Serverzugang nötig
Nachteile
- Auf Shared Hosting oft deaktiviert
- Löst nicht das Memory-Problem
- Unsauber — der Server blockiert andere Prozesse
Lösung 2: php.ini / .user.ini anpassen
Falls du Zugriff auf die PHP-Konfiguration hast, kannst du das Zeitlimit dort setzen:
Variante A: php.ini (Root-Server)
; /etc/php/8.1/apache2/php.ini
max_execution_time = 300
memory_limit = 512M
post_max_size = 256M
upload_max_filesize = 256M
Danach Apache/PHP-FPM neu starten:
sudo systemctl restart apache2
# oder bei PHP-FPM:
sudo systemctl restart php8.1-fpm
Variante B: .user.ini (Shared Hosting)
Viele Hoster erlauben eine .user.ini im Webroot. Erstelle die Datei im Verzeichnis deines Export-Scripts:
; .user.ini
max_execution_time = 300
memory_limit = 256M
Achtung: Die .user.ini wird gecacht (Standard: 300 Sekunden). Änderungen greifen nicht sofort.
Variante C: .htaccess (Apache)
# .htaccess
php_value max_execution_time 300
php_value memory_limit 256M
Funktioniert nur bei Apache mit mod_php. Bei PHP-FPM oder Nginx hat die .htaccess keinen Effekt auf PHP-Werte.
Was die Hoster erlauben
| Hoster | php.ini ändern | .user.ini | .htaccess PHP |
|---|---|---|---|
| Strato | Nein | Ja (begrenzt) | Nein |
| IONOS | Über Panel | Ja | Teilweise |
| all-inkl | Über Panel | Ja | Ja |
| Hetzner | Ja (VPS) | Ja | Ja |
Lösung 3: Chunked Export (der MySQLDumper-Ansatz)
Das ist die eleganteste Lösung und der Grund, warum MySQLDumper existiert. Statt den gesamten Export in einem einzigen PHP-Aufruf zu machen, wird der Prozess in kleine Teile aufgesplittet.
Das Prinzip
- Script exportiert z.B. 2.000 Zeilen einer Tabelle
- Speichert den aktuellen Fortschritt (Tabelle, Offset)
- Leitet den Browser per HTTP-Redirect auf sich selbst um
- Der nächste Aufruf macht die nächsten 2.000 Zeilen
- Wiederholen bis alles fertig ist
Jeder einzelne Aufruf bleibt unter dem Zeitlimit. Ob deine Datenbank 10 MB oder 10 GB groß ist — spielt keine Rolle.
Vereinfachtes Beispiel
<?php
session_start();
$host = 'localhost';
$user = 'root';
$pass = 'passwort';
$dbname = 'meine_datenbank';
$chunk_size = 2000;
$output_file = 'backup.sql';
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass);
// Fortschritt aus Session laden
$table_index = $_SESSION['table_index'] ?? 0;
$row_offset = $_SESSION['row_offset'] ?? 0;
// Alle Tabellen holen
$tables = $pdo->query("SHOW TABLES")->fetchAll(PDO::FETCH_COLUMN);
if ($table_index >= count($tables)) {
// Fertig!
echo "Export abgeschlossen!";
session_destroy();
exit;
}
$table = $tables[$table_index];
// Chunk exportieren
$stmt = $pdo->query("SELECT * FROM `$table` LIMIT $row_offset, $chunk_size");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
// In Datei schreiben (append)
$fh = fopen($output_file, 'a');
foreach ($rows as $row) {
$values = array_map(function($v) use ($pdo) {
return $v === null ? 'NULL' : $pdo->quote($v);
}, $row);
fwrite($fh, "INSERT INTO `$table` VALUES (" . implode(',', $values) . ");\n");
}
fclose($fh);
// Fortschritt speichern
if (count($rows) < $chunk_size) {
// Tabelle fertig → nächste Tabelle
$_SESSION['table_index'] = $table_index + 1;
$_SESSION['row_offset'] = 0;
} else {
$_SESSION['row_offset'] = $row_offset + $chunk_size;
}
// Nächsten Chunk starten
$progress = round(($table_index / count($tables)) * 100);
echo "Exportiere $table... ($progress%)
";
echo '<meta http-equiv="refresh" content="0">';
?>
Hinweis: Das ist eine vereinfachte Version zur Illustration. MySQLDumper macht das deutlich robuster — mit Fehlerbehandlung, Komprimierung, CREATE TABLE Statements und korrektem Escaping.
Vorteile
- Funktioniert auf jedem Shared Hosting
- Keine Serverkonfiguration nötig
- Keine Datenbankgröße-Beschränkung
- Fortschrittsanzeige im Browser
Nachteile
- Langsamer als ein direkter Export
- Browser muss offen bleiben (bei Browser-basiertem Redirect)
- Konsistenz kann bei gleichzeitigen Schreibvorgängen leiden
Lösung 4: SSH und mysqldump
Wenn du SSH-Zugang hast, ist das Timeout-Problem keins. mysqldump läuft als eigenständiger Prozess auf dem Server — PHP ist nicht involviert.
mysqldump --single-transaction -u root -p meine_datenbank | gzip > backup.sql.gz
Falls du den Dump im Hintergrund laufen lassen willst:
nohup mysqldump --single-transaction -u root -p meine_datenbank | gzip > backup.sql.gz &
nohup sorgt dafür, dass der Prozess weiterläuft, auch wenn du die SSH-Verbindung schließt. Alternativ mit screen oder tmux:
# Screen-Session starten
screen -S backup
# Dump starten
mysqldump --single-transaction -u root -p meine_datenbank | gzip > backup.sql.gz
# Session trennen: Ctrl+A, dann D
# Später wieder verbinden:
screen -r backup
Vorteile
- Kein Timeout — der Prozess läuft so lange er braucht
- Volle Kontrolle über den Server
- Schnellste Methode
- Konsistente Backups mit
--single-transaction
Nachteile
- Braucht SSH-Zugang (auf Shared Hosting meist nicht verfügbar)
- Kommandozeilen-Erfahrung nötig
Welche Lösung für welche Situation?
| Situation | Empfehlung |
|---|---|
| Shared Hosting, kein SSH | Chunked Export (MySQLDumper) |
| Shared Hosting, set_time_limit erlaubt | set_time_limit + phpMyAdmin |
| VPS/Root-Server | SSH + mysqldump |
| Managed Hosting mit Panel | php.ini über Panel + phpMyAdmin |
| Datenbank > 500 MB | SSH + mysqldump, kein Weg dran vorbei |
Bonus: Import-Timeout lösen
Das Timeout-Problem existiert auch beim Import. Wenn du einen 200-MB-Dump über phpMyAdmin importieren willst, hast du dasselbe Problem. Die Lösungen sind analog:
- MySQLDumper macht auch den Restore chunked
- BigDump (bigdump.php) ist ein spezialisiertes Import-Script mit Chunk-Funktion
- SSH:
mysql -u root -p datenbank < backup.sql— kein Timeout
Mein Rat: Wenn du regelmäßig mit Datenbanken arbeitest, die größer als 50 MB sind, lohnt sich die Investition in einen Hoster mit SSH-Zugang. Das spart dir auf Dauer Stunden an Workarounds. Einen Überblick über alle Backup-Methoden und ihre Vor- und Nachteile findest du im Hauptartikel.