yiisoft/yii2 · error · yii\base\Exception
Failed to change permissions for directory "{$path}": {messa
Error message
Failed to change permissions for directory "{$path}": {message} What it means
Thrown by yii\helpers\BaseFileHelper::createDirectory() when the directory was created (or already existed) but the subsequent native chmod($path, $mode) call raised an exception. Yii re-throws it as yii\base\Exception with chmod's message. Native chmod() normally only warns and returns false, so the throw is reached when an error handler converts the E_WARNING to an ErrorException (common under Yii's ErrorHandler or strict custom handlers) or when PHP 8 raises a ValueError/TypeError for an invalid $mode argument.
Source
Thrown at framework/helpers/BaseFileHelper.php:726
}
$parentDir = dirname($path);
// recurse if parent dir does not exist and we are not at the root of the file system.
if ($recursive && !is_dir($parentDir) && $parentDir !== $path) {
static::createDirectory($parentDir, $mode, true);
}
try {
if (!mkdir($path, $mode)) {
return false;
}
} catch (\Exception $e) {
if (!is_dir($path)) {// https://github.com/yiisoft/yii2/issues/9288
throw new \yii\base\Exception("Failed to create directory \"$path\": " . $e->getMessage(), $e->getCode(), $e);
}
}
try {
return chmod($path, $mode);
} catch (\Exception $e) {
throw new \yii\base\Exception("Failed to change permissions for directory \"$path\": " . $e->getMessage(), $e->getCode(), $e);
}
}
/**
* Performs a simple comparison of file or directory names.
*
* Based on match_basename() from dir.c of git 1.8.5.3 sources.
*
* @param string $baseName file or directory name to compare with the pattern
* @param string $pattern the pattern that $baseName will be compared against
* @param int|bool $firstWildcard location of first wildcard character in the $pattern
* @param int $flags pattern flags
* @return bool whether the name matches against pattern
*/
private static function matchBasename($baseName, $pattern, $firstWildcard, $flags)
{
if ($firstWildcard === false) {
if ($pattern === $baseName) {View on GitHub (pinned to 66f00d18a2)
Solutions
- Fix ownership so the PHP process owns the directory it must chmod: sudo chown -R www-data:www-data <dir> (only the owner may chmod), then retry createDirectory.
- Pass the mode as an octal integer literal (0775), never a string ('0775') — on PHP 8 the wrong type raises ValueError/TypeError before chmod runs.
- If the directory already exists with correct permissions, skip the mode-enforcing call: guard with if (!is_dir($path)) { FileHelper::createDirectory($path, $mode); } since the chmod branch only runs after a fresh mkdir.
- On NFS/ACL/immutable filesystems, adjust the export options (no_root_squash for service accounts), clear chattr +i, or pre-create the directory with the desired mode from the owning side.
- If a global set_error_handler converts every E_WARNING to exceptions, exempt filesystem functions or wrap createDirectory in a handler that tolerates chmod warnings when is_dir($path) already holds.
Example fix
// before
FileHelper::createDirectory($cachePath, '0777'); // string mode -> PHP 8 ValueError caught and re-thrown here
// after
$mode = 0775; // octal int
if (!is_dir($cachePath)) {
FileHelper::createDirectory($cachePath, $mode);
} Defensive patterns
Strategy: validation
Validate before calling
$mode = 0775; // octal int, never a string
if (!is_int($mode) || $mode < 0 || $mode > 07777) {
throw new InvalidArgumentException('mode must be an octal int like 0775');
}
if (is_dir($path)) {
$owner = fileowner($path);
$current = posix_geteuid();
if ($owner !== false && $current !== false && $owner !== $current && !function_exists('posix_geteuid') === false && $owner !== $current) {
// chmod will fail: only the owner may change mode
clearstatcache(true, $path);
if (fileowner($path) !== $current) {
Yii::warning("Cannot chmod $path: owned by uid " . fileowner($path) . ", running as $current");
}
}
} else {
$parent = dirname($path);
while (!is_dir($parent)) { $parent = dirname($parent); }
if (!is_writable($parent)) {
throw new RuntimeException("Parent $parent not writable; mkdir would fail first");
}
} Type guard
function modeIsSafeForChmod(int|string $mode): bool
{
return is_int($mode) && $mode >= 0 && $mode <= 07777;
}
function directoryOwnedByCurrentProcess(string $path): bool
{
clearstatcache(true, $path);
return is_dir($path) && fileowner($path) === posix_geteuid(); // false => chmod() cannot succeed
} Try / catch
try {
FileHelper::createDirectory($path, 0775, true);
} catch (\yii\base\Exception $e) {
if (strpos($e->getMessage(), 'Failed to change permissions') === 0 && is_dir($path)) {
// Directory exists; only the mode could not be enforced (owner mismatch / ACL).
Yii::warning("{$path} created but mode not applied: {$e->getMessage()}", __METHOD__);
} else {
throw $e; // creation itself failed — do not swallow
}
} Prevention
- Create runtime/cache directories during provisioning with the exact final ownership and mode, so the chmod in createDirectory is a no-op on an existing dir.
- Never pass $mode as a string ('0775'); always the octal literal 0775 — PHP 8 raises ValueError for bad types.
- Ensure the PHP-FPM/CLI user owns every directory createDirectory will chmod; only the owner may chmod, regardless of write bits.
- On NFS or hardened hosts, verify with `lsattr` that the target is not immutable and that ACLs (getfacl) grant the web user control.
- Log fileowner($path) vs posix_geteuid() at startup for each writable path; a mismatch predicts this exact exception.
When it happens
Trigger: Calling FileHelper::createDirectory($path, $mode) when: the directory exists but is owned by a different user than the PHP process (chmod only allowed for the owner); the filesystem is NFS/root-squash, a read-only bind mount, or has restrictive ACLs/immutable attributes; $mode is passed as a non-octal string like '0777' or an invalid int on PHP 8 (ValueError); or a set_error_handler turns chmod()'s warning into ErrorException.
Common situations: A deploy pipeline or cron job pre-creates runtime/cache dirs as root, then php-fpm as www-data cannot chmod them; NFS shares with root_squash mapping the web user to nobody; directories with the immutable flag (chattr +i) from hardening; developers passing the mode as a string '0775' instead of the octal literal 0775; upgrades to PHP 8 surfacing argument type errors that PHP 7 ignored.
Related errors
- Failed to create directory "{$path}": {message}
- FileDependency::fileName must be set
- Unable to open directory: $dir
- Unable to change ownership, "{$path}" is not a file or direc
- $mode must be an integer or null.
AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17).
Data as JSON: /api/errors/d58328d3d6a0603f.
Report an issue: GitHub.