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

  1. Fix the file's ownership/mode: chown www-data:www-data <file> && chmod 664 <file> (and its directory 775).
  2. If the file is a stale/regenerable artifact, remove it so 'c' mode recreates it with correct ownership.
  3. Verify the path is within open_basedir and check SELinux audit logs (restorecon -v on the path).
  4. 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

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


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/0a17e60f50db4b55. Report an issue: GitHub.