getgrav/grav · error · RuntimeException
Opening file for writing failed on error {$message}
Error message
Opening file for writing failed on error {$message} What it means
After the directory exists, AbstractFile::lock() opens the target with @fopen($filepath, 'cb+') (read/write, create if missing, no truncation); on failure it embeds PHP's last error message (error_get_last()) and throws. Because the directory step already succeeded, this failure is about the file itself: it exists but is not writable by the PHP user, the path is blocked by open_basedir, the 'file' is actually a directory, or a MAC layer (SELinux) denies access.
Source
Thrown at system/src/Grav/Framework/File/AbstractFile.php:198
return is_file($this->filepath) ? (int)filemtime($this->filepath) : time();
}
/**
* {@inheritdoc}
* @see FileInterface::lock()
*/
public function lock(bool $block = true): bool
{
if (!$this->handle) {
if (!$this->mkdir($this->getPath())) {
throw new RuntimeException('Creating directory failed for ' . $this->filepath);
}
$this->handle = @fopen($this->filepath, 'cb+') ?: null;
if (!$this->handle) {
$error = error_get_last();
$message = $error['message'] ?? 'Unknown error';
throw new RuntimeException("Opening file for writing failed on error {$message}");
}
}
$lock = $block ? LOCK_EX : LOCK_EX | LOCK_NB;
// Some filesystems do not support file locks, only fail if another process holds the lock.
$this->locked = flock($this->handle, $lock, $wouldBlock) || !$wouldBlock;
return $this->locked;
}
/**
* {@inheritdoc}
* @see FileInterface::unlock()
*/
public function unlock(): bool
{
if (!$this->handle) {View on GitHub (pinned to 6040efed04)
Solutions
- Fix the file's ownership/mode: chown www-data:www-data <file> && chmod 664 <file> (and its directory 775).
- If the file is a stale/regenerable artifact, remove it so 'c' mode recreates it with correct ownership.
- Verify the path is within open_basedir and check SELinux audit logs (restorecon -v on the path).
- Run CLI and web PHP as the same user to prevent ownership flips.
Example fix
# before: "Opening file for writing failed on error fopen(...): failed to open stream: Permission denied" ls -l /var/www/grav/user/data/plugin/x.json # -rw-r--r-- root root sudo chown www-data:www-data /var/www/grav/user/data/plugin/x.json sudo chmod 664 /var/www/grav/user/data/plugin/x.json # after: lock() succeeds
Defensive patterns
Strategy: validation
Validate before calling
$path = $file->getFilePath();
if (file_exists($path) ? !is_writable($path) : !is_writable(dirname($path))) {
// fopen('cb+') in lock() will fail: fix ownership/mode first
} Try / catch
try {
$file->lock();
} catch (\RuntimeException $e) {
// message embeds PHP's own fopen error; log it verbatim for the admin
$log->error($e->getMessage());
} Prevention
- Never run bin/grav as root — files become root-owned and unwritable by the web user.
- After restores/migrations, chown -R the install to the PHP user.
- Check open_basedir and SELinux contexts when adding new storage paths.
When it happens
Trigger: lock() on a file created earlier by a different user (root CLI run leaving root-owned files); file mode 644 owned by another user while PHP runs as www-data; a directory occupying the intended file path; open_basedir excluding the path; SELinux context denials on the file.
Common situations: Mixing bin/grav CLI runs as root with web requests; restoring backups that reset ownership/modes; hardened shared hosting; container images with wrong volume ownership (files baked in as root).
Related errors
- Creating directory failed for {filepath}
- Unable to %s: %s
- Unable to create directory: %s
- Failed to save file {filepath}
- Bad Data
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/0a17e60f50db4b55.
Report an issue: GitHub.