unlink
Deletes a file from the filesystem.
unlink(string $filename, ?resource $context = null): bool- Added in PHP 4.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$filename | string | Path to the file. If it is a symbolic link, the link itself is deleted, not its target. |
$contextoptional | ?resource= null | A stream context resource created with stream_context_create(), used by wrappers that support deletion. |
Return value
boolReturns true on success or false on failure.
Errors
Emits an E_WARNING on failure, for example when the file does not exist or the containing directory is not writable.
Changelog
- PHP 7.3.0On Windows it is now possible to unlink() files that still have open handles, though the name cannot be reused until every handle is closed.
unlink() removes a directory entry, exactly like the C function of the same name. The data is only actually freed once the link count reaches zero and no process holds the file open — which is why deleting a file another process is reading is safe on Unix: the reader keeps working against the now-nameless inode.
When to reach for it
- Removing an upload after it has been processed or moved to permanent storage.
- Expiring cache and session files.
- Cleaning up temp files created by
tempnam(), which nothing else deletes.
What trips people up
Deletion is governed by the directory's permissions, not the file's. You can delete a read-only file in a directory you can write to, and you cannot delete a world-writable file in a directory you cannot write to.
It only handles files. Passing a directory yields false and a warning — use rmdir(). On Windows, a symlink that points at a directory must also go through rmdir(), even though it is a link.
A missing file counts as failure and emits E_WARNING. If the file's absence is acceptable, guard with is_file() rather than reaching for the @ operator, which hides real errors too. And remember the stat cache: file_exists() can still return true immediately after a successful delete until you call clearstatcache().
Example
When to use it
- Delete the original upload once a resized copy has been written to permanent storage.
- Purge cache files whose mtime is older than the TTL during a scheduled cleanup run.
- Remove the temp file created by tempnam() in a finally block so failures do not leak disk space.
Frequently asked questions
Why does unlink say permission denied when the file is writable?
Deleting is controlled by the permissions on the directory that holds the file, not the file itself. You need write and execute permission on that directory.
How do I delete a directory with unlink?
You cannot — unlink() only removes files and returns false with a warning on a directory. Use rmdir(), and empty the directory first. On Windows, symlinks to directories also require rmdir().
Does unlink delete a symlink or the file it points to?
It deletes the symlink itself and leaves the target intact. That matches the C unlink() behaviour, and is why removing a symlinked cache entry never destroys the real data.
More examples
Delete only when the file is really a file
The is_file() guard keeps directories and missing paths from reaching unlink(), so no E_WARNING is emitted and no @ suppression is needed.
<?php
file_put_contents('demo.txt', 'x');
mkdir('demo_dir');
foreach (['demo.txt', 'demo_dir', 'nope.txt'] as $p) {
if (is_file($p)) {
echo $p, ': ', unlink($p) ? "deleted\n" : "failed\n";
} else {
echo $p, ": not a plain file, skipped\n";
}
}
rmdir('demo_dir');An open handle keeps working after the delete
On Unix, unlink() removes the name while the open handle keeps the inode alive, so the read still succeeds. This is exactly how tmpfile() gets its self-cleaning behaviour.
<?php
file_put_contents('demo.txt', "still readable\n");
$fh = fopen('demo.txt', 'r');
unlink('demo.txt');
clearstatcache();
var_dump(file_exists('demo.txt'));
echo fgets($fh); // the inode is still alive
fclose($fh);Expire cache files by age
Only the backdated file crosses the one-hour threshold, so it is unlinked while the fresh one survives. This is the core of most file-based cache garbage collectors.
<?php
file_put_contents('old.cache', 'stale');
file_put_contents('new.cache', 'fresh');
touch('old.cache', time() - 7200);
clearstatcache();
foreach (['old.cache', 'new.cache'] as $f) {
if (time() - filemtime($f) > 3600) {
unlink($f);
echo "removed $f\n";
} else {
echo "kept $f\n";
}
}
unlink('new.cache');
Discussion