Im Adminbereich wird ein Menüpunkt umbenannt, und auf der Website steht noch eine Stunde lang der alte Name, weil der Seitencache seine Dateien erst nach Ablauf erneuert. Statt alle Cache-Dateien zu löschen, genügt ein Aufruf, der das Datum einer Markerdatei auf jetzt setzt. Wie man das Änderungsdatum einer Datei ausliest, zeigen filemtime() und getlastmod(). Hier geht es um die Gegenrichtung: PHP touch() setzt dieses Datum gezielt, auf jetzt, in die Vergangenheit oder auf den Wert einer anderen Datei.
Eine Datei trägt drei Zeitstempel, und nur zwei davon lässt PHP touch() verstellen, auch das nicht jedem Benutzer.
Was die PHP touch() Function macht: Zeit setzen und Datei anlegen
Die Funktion hat zwei Rollen. Existiert die Datei, setzt sie deren Änderungszeit und Zugriffszeit, und der Inhalt bleibt dabei unangetastet. Existiert die Datei nicht, legt PHP touch() sie leer an, filesize() liefert danach 0. Das ist der Unterschied zu fopen() im Modus w, der eine vorhandene Datei bei jedem Aufruf leert.
<?php
$datei = __DIR__ . '/daten/gaestebuch.txt';
if (!touch($datei)) {
exit("Datei nicht angelegt\n");
}
var_dump(filesize($datei));
/* int(0) beim ersten Aufruf,
danach die bisherige Größe */
Ein fehlendes Verzeichnis legt die Funktion nicht an. Der Aufruf liefert dann false und die Warnung touch(): Unable to create file ... because No such file or directory, unter Windows wie unter Linux. Für den Ordner ist vorher mkdir() mit dem Parameter recursive zuständig.
Ob ein Aufruf anlegt, nur die Zeit setzt oder scheitert, entscheiden drei Fragen in dieser Reihenfolge:
flowchart TD
A[touch mit Pfad] --> B{Datei vorhanden?}
B -->|nein| C{Verzeichnis beschreibbar?}
C -->|ja| D[leere Datei anlegen]
C -->|nein| E[false und Warnung]
B -->|ja| F{Zeit übergeben?}
F -->|nein| G[jetzt, Schreibrecht reicht]
F -->|ja| H{Eigentümer der Datei?}
H -->|ja| I[mtime und atime setzen]
H -->|nein| J[Operation not permitted]
D --> I
Die Signatur im Original: $filename, $mtime, $atime
Nur der Dateiname ist Pflicht:
touch(
string $filename,
?int $mtime = null,
?int $atime = null
): bool
/* $filename path; created empty if missing
$mtime modification time, null = now
$atime access time, null = same as $mtime
returns true on success, false on failure
This function sets the access time and the
modification time of a file or a directory.
Trying to set a time other than now needs
the permissions of the file owner. */
Wer nur $mtime übergibt, setzt die Zugriffszeit auf denselben Wert, nicht auf jetzt und auch nicht auf den alten Stand. Gemessen auf beiden Plattformen: touch($f, 1000000000) ergab mtime und atime 1000000000. Gedacht ist PHP touch() für lokale Dateien; ob ein Stream-Wrapper wie ftp:// es unterstützt, hängt vom Wrapper ab.
mtime, atime und ctime: welche Zeit PHP setzen kann
Unter Linux und macOS führt das Dateisystem drei Zeiten. Nur zwei davon nimmt PHP touch() als Parameter an:
| Zeit | Bedeutung | Auslesen mit | Mit touch() setzbar |
| mtime | letzte Änderung des Inhalts | filemtime() | ja, zweiter Parameter |
| atime | letzter Lesezugriff | fileatime() | ja, dritter Parameter |
| ctime | letzte Änderung der Metadaten wie Rechte, Besitzer und Zeiten | filectime() | nein, setzt das Dateisystem selbst |
Dass die ctime fehlt, ist keine Lücke in PHP. Das Dateisystem setzt sie bei jeder Metadatenänderung auf jetzt, und ein Aufruf von PHP touch() ist selbst eine solche Änderung. Ein Versuch zeigt es: Nach dem Setzen von mtime auf 2020 und atime auf 2021 stand die ctime auf der aktuellen Uhrzeit. Unter Windows meint filectime() laut Handbuch die Erstellungszeit. Für Aufräumroutinen taugt die atime wenig, weil viele Linux-Server ihre Laufwerke mit noatime oder relatime einhängen und die Zugriffszeit beim Lesen dann kaum noch nachführen. Die mtime ist robuster.
Einen Zeitstempel in die Vergangenheit setzen
Wer einen Dateicache testet, will nicht eine Stunde warten, bis eine Datei abgelaufen ist. Ein Zeitstempel in der Vergangenheit lässt sie sofort altern, und strtotime() erspart das Rechnen in Sekunden:
<?php
$datei = __DIR__ . '/cache/start.html';
touch($datei, strtotime('-1 hour'));
echo date('d.m.Y H:i', filemtime($datei));
/* eine Stunde vor der aktuellen Uhrzeit */
Soll die Zugriffszeit dabei erhalten bleiben, wird sie ausdrücklich mitgegeben. Umgekehrt geht es nur über einen Umweg, denn PHP 8 lehnt eine leere Änderungszeit neben einer gesetzten Zugriffszeit ab:
<?php
$neu = strtotime('2026-01-01 08:00');
/* nur mtime ändern, atime behalten */
touch($datei, $neu, fileatime($datei));
/* nur atime ändern, mtime behalten */
touch($datei, filemtime($datei), $neu);
touch($datei, null, $neu);
/* ValueError: touch(): Argument #2 ($mtime)
cannot be null when argument #3
($atime) is an integer */
Zeiten vor 1970 gehören nicht in einen solchen Aufruf. Unter Linux wurde -100 korrekt gesetzt, unter Windows meldete PHP touch() Erfolg und hinterließ einen unsinnigen Wert.
Der stat-Cache: wann clearstatcache() nötig ist
PHP merkt sich die Ergebnisse von filemtime(), filesize() und Verwandten. Daraus ist die Faustregel entstanden, nach jedem touch() müsse clearstatcache() folgen. Die Messung widerspricht: Nach dem eigenen Aufruf lieferte filemtime() unter Windows und Linux sofort den neuen Wert. PHP verwirft den Zwischenspeicher dabei selbst, ebenso nach eigenem Schreiben mit fwrite().
Anders sieht es aus, wenn ein anderer Prozess die Datei ändert. Im folgenden Lauf setzte ein Hintergrundprozess die Zeit während der Pause neu, und das Skript sah davon nichts:
<?php
$marker = __DIR__ . '/import.ok';
$alt = filemtime($marker);
sleep(3);
/* in dieser Pause setzt ein anderer
Prozess die Zeit der Datei neu */
var_dump(filemtime($marker) === $alt);
/* bool(true), noch der alte Wert */
clearstatcache(true, $marker);
var_dump(filemtime($marker) === $alt);
/* bool(false), jetzt der neue Wert */
Die Regel lautet also nicht „nach PHP touch() immer leeren“, sondern: clearstatcache(true, $pfad) vor dem Lesen, wenn ein anderer Prozess die Datei angefasst haben kann. Genau diesen Fall zeigt auch das Tutorial zu filemtime() in seinem Cache-Abschnitt. Wer ihn nach jedem eigenen touch() einbaut, schreibt toten Code.
Wer einen Zeitstempel setzen darf
Auf Linux-Servern verwirrt regelmäßig dieser Fall: Die Datei ist für alle beschreibbar, file_put_contents() funktioniert, aber PHP touch() mit einem Datum scheitert. Unter Apache und nginx läuft PHP meist als User www-data, ein Cronjob über die CLI dagegen oft als ein anderer. Im Container wurde der Fall als www-data nachgestellt, an einer Datei, die root gehört:
| Aufruf | Ergebnis |
Modus 0666, touch($f) | true |
Modus 0666, touch($f, 1000000000) | false, Utime failed: Operation not permitted |
Modus 0666, touch($f, time()) | false, Utime failed: Operation not permitted |
Modus 0644, touch($f) | false, Utime failed: Permission denied |
| neue Datei, Verzeichnis ohne Schreibrecht | false, Unable to create file ... because Permission denied |
Die Zeit auf jetzt setzen darf, wer schreiben darf. Eine bestimmte Zeit setzen darf nur der Eigentümer der Datei oder root. Bemerkenswert ist die dritte Zeile: Auch time() als Zahl gilt als beliebige Zeit, obwohl der Wert derselbe ist. Nur der Aufruf ganz ohne Zeitangabe nutzt die Ausnahme für Schreibberechtigte. Wer Dateien per FTP mit einem anderen Benutzer hochlädt, kann ihre Zeit aus dem Webserver heraus also nicht verstellen. fileowner() verrät, wem eine Datei gehört. Im Code sieht die Fehlerbehandlung so aus:
<?php
if (!touch($datei, $zeit)) {
$fehler = error_get_last();
error_log('touch: ' . ($fehler['message'] ?? '?'));
/* wenigstens auf jetzt setzen */
touch($datei);
}
Praxis: Cache-Dateien und Cronjob-Marker mit PHP touch()
Zurück zum serverseitigen Seitencache aus der Einleitung. Statt Dateien zu löschen, legt man eine Markerdatei an, etwa cache.stamp, und setzt ihre Zeit bei jeder Änderung im Adminbereich mit touch($marker) auf jetzt. Jede Cache-Datei, die älter ist als der Marker, gilt ab diesem Moment als veraltet:
<?php
function istVeraltet(
string $cache,
string $marker
): bool {
if (!is_file($cache)) {
return true;
}
if (!is_file($marker)) {
return false;
}
return filemtime($cache) <= filemtime($marker);
}
Das <= statt < hat einen Grund: filemtime() zählt in ganzen Sekunden. Entsteht eine Cache-Datei in derselben Sekunde, in der der Marker gesetzt wird, gilt sie sicherheitshalber als veraltet und wird einmal zu oft neu gebaut. Ein einziger Aufruf von PHP touch() entwertet so beliebig viele Dateien, ohne eine davon anzufassen.
Dasselbe Prinzip überwacht Cronjobs. Jeder Job setzt am Ende eines erfolgreichen Laufs seine eigene Markerdatei, also ganz zuletzt und nicht am Anfang, sonst meldet auch ein abgestürzter Lauf Erfolg. Unter Windows übernimmt die Aufgabenplanung die Rolle von cron. Die Prüfseite vergleicht das Alter jedes Markers mit einer Schwelle:
<?php
$jobs = [
'newsletter' => 3600,
'sicherung' => 86400,
];
foreach ($jobs as $job => $schwelle) {
$m = __DIR__ . "/marker/$job.ok";
clearstatcache(true, $m);
if (!is_file($m)) {
echo "$job: noch nie gelaufen\n";
continue;
}
$alter = time() - filemtime($m);
$stand = $alter > $schwelle
? 'ÜBERFÄLLIG'
: 'ok';
printf(
"%s: %s, vor %d Min.\n",
$job,
$stand,
intdiv($alter, 60)
);
}
Hier steht clearstatcache() zu Recht, denn die Marker schreibt ein anderer Prozess. Bei einem einzelnen Seitenaufruf schadet die Zeile nicht, und sobald die Prüfung als Dauerschleife läuft, ist sie Pflicht.
Zeitstempel beim Kopieren erhalten
Ein verbreiteter Irrtum lautet, copy() behalte das Änderungsdatum. Gemessen auf beiden Plattformen trug die Kopie einer Datei vom Januar 2008 die aktuelle Uhrzeit. Für einen inkrementellen Abgleich sieht damit jede Kopie neu aus. PHP touch() stellt die Zeiten der Quelle nachträglich her:
<?php
$quelle = __DIR__ . '/archiv/rechnung.pdf';
$ziel = __DIR__ . '/sicherung/rechnung.pdf';
if (!copy($quelle, $ziel)) {
exit("Kopie fehlgeschlagen\n");
}
$ok = touch(
$ziel,
filemtime($quelle),
fileatime($quelle)
);
if (!$ok) {
error_log("Zeit nicht übernommen: $ziel");
}
Danach trug das Ziel wieder den Zeitstempel der Quelle. Ein rename() braucht diese Nacharbeit nicht, die Zeit blieb unter Windows gemessen erhalten. Dasselbe Muster hilft bei Dateien, die aus Datenbankinhalten erzeugt werden. Ohne Nacharbeit trägt nach jedem Neuaufbau das ganze Archiv das heutige Datum, deshalb kommt das Datum aus dem Datensatz:
<?php
file_put_contents($datei, $html);
$zeit = strtotime($zeile['geaendert']);
if ($zeit !== false) {
touch($datei, $zeit);
}
Der Webserver liefert danach die richtige Angabe im Header Last-Modified aus. Was Browser damit anfangen, erklärt das Tutorial zu getlastmod() und dem Status 304. Auch hier gilt die Rechteregel: Die Datei muss dem Benutzer gehören, unter dem das Skript läuft.
Sperrdateien: warum PHP touch() kein flock() ist
Gelegentlich steht in Importskripten dieses Muster: Gibt es die Sperrdatei nicht, wird sie per touch() angelegt, am Ende per unlink() entfernt. Prüfen und Anlegen sind aber zwei Schritte, und zwischen beide passt ein zweiter Prozess. Dann laufen zwei Importe gleichzeitig. Stürzt ein Lauf ab, bleibt die Datei liegen und blockiert alle weiteren, bis jemand sie von Hand löscht.
Die saubere Sperre ist flock(): Das Betriebssystem vergibt sie in einem Schritt und gibt sie frei, sobald der Prozess endet. PHP touch() hat in diesem Umfeld trotzdem einen Platz, und zwar als Lebenszeichen. Ein Worker aktualisiert die Zeit seiner Statusdatei alle paar Minuten. Steht die mtime seit einer Stunde still, hängt der Worker.
PHP touch() und der Unix-Befehl touch
Unter Linux und macOS gibt es ein Kommandozeilenwerkzeug gleichen Namens, und in älterem Code steht deshalb manchmal exec('touch ...'). Das ist fast nie die bessere Wahl. Die PHP-Funktion braucht keine Shell, kein exec() und keinen Schutz der Argumente, und sie läuft auch unter Windows, wo es den Befehl nicht gibt. Die Schalter des Befehls lassen sich alle nachbilden:
| Kommandozeile | In PHP |
touch -d "2026-01-01" f | touch($f, strtotime('2026-01-01')) |
touch -r quelle f | touch($f, filemtime($q), fileatime($q)) |
touch -m f, nur mtime | touch($f, time(), fileatime($f)) |
touch -a f, nur atime | touch($f, filemtime($f), time()) |
touch -c f, nicht anlegen | if (file_exists($f)) touch($f); |
Unter Windows funktioniert PHP touch() im Kern genauso, mit einer gemessenen Eigenheit. Eine schreibgeschützte Datei liefert false und eine Meldung der Form Utime failed: …, deren Begründung nicht stimmt. Im Test stand dort je nach dem vorherigen Aufruf No error, No such file or directory oder File exists. Wer unter Windows ein false von PHP touch() bekommt, prüft deshalb zuerst das Schreibschutz-Attribut und nicht den Pfad. Auf ein vorhandenes Verzeichnis angewandt, setzt die Funktion auf beiden Plattformen dessen Zeit und liefert true.
Fazit
PHP touch() ist das Gegenstück zu filemtime(). Es legt eine fehlende Datei leer an, lässt vorhandenen Inhalt in Ruhe und setzt mtime und atime auf jetzt, auf einen Wert aus der Vergangenheit oder auf die Zeiten einer anderen Datei. Die ctime bleibt Sache des Dateisystems.
Wer nur die Änderungszeit übergibt, setzt die Zugriffszeit mit. Nach dem eigenen Aufruf ist kein clearstatcache() nötig, wohl aber vor dem Lesen einer Datei, die ein anderer Prozess anfasst. Und eine bestimmte Zeit setzen darf nur der Eigentümer, selbst wenn die Datei für alle beschreibbar ist. Damit werden Markerdateien zum billigsten Werkzeug für Cache-Invalidierung und Cronjob-Überwachung. Als Sperre taugen sie nicht, dafür gibt es flock().