{"record":{"id":"0982c62dbcfc5dc6","repo":"phalcon/cphalcon","slug":"the-session-save-path-path-is-not-writable","errorCode":null,"errorMessage":"The session save path [{path}] is not writable","messagePattern":"The session save path \\[(.+?)\\] is not writable","errorType":"exception","errorClass":"Phalcon\\Session\\Adapter\\Exceptions\\SavePathUnavailable","httpStatus":null,"severity":"error","filePath":"phalcon/Session/Adapter/Stream.zep","lineNumber":98,"sourceCode":"    public function __construct( array options = [])\n    {\n        var path;\n\n        let this->prefix  = this->getArrVal(options, \"prefix\", \"\"),\n            this->options = options;\n\n        /**\n         * Get the save_path from the passed options. If not defined\n         * get it from php.ini\n         */\n        let path = this->getArrVal(options, \"savePath\", this->phpIniGet(\"session.save_path\"));\n\n        if unlikely true === empty(path) {\n            throw new InvalidSavePath();\n        }\n\n        if unlikely true !== this->phpIsWritable(path) {\n            throw new SavePathUnavailable(path);\n        }\n\n        let this->path = this->toDirSeparator(path);\n    }\n\n    public function destroy(string id) -> bool\n    {\n        var file;\n\n        let file = this->path . this->getPrefixedName(id);\n\n        if this->phpFileExists(file) && is_file(file) {\n            this->phpUnlink(file);\n        }\n\n        return true;\n    }\n","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/phalcon/cphalcon/blob/b7419de9cd0a8a3f48441ead84c9f8415d463e25/phalcon/Session/Adapter/Stream.zep#L80-L116","documentation":"SavePathUnavailable is thrown by the Session\\Adapter\\Stream constructor when the resolved save path exists in configuration but PHP's is_writable() check fails. The process running PHP (www-data, apache, cli user) has no write permission on the directory that holds session files.","triggerScenarios":"savePath points to a directory owned by root while PHP-FPM runs as www-data; directory mode 755 with no write for the PHP user; open_basedir excludes the path; SELinux denies writes; the path is a read-only volume in a container.","commonSituations":"Manually created session directory with wrong ownership; switching from mod_php (user apache) to PHP-FPM (user www-data) without chown-ing the session directory; hardened shared hosting with open_basedir; systemd PrivateTmp hiding the expected /tmp to other services.","solutions":["Fix ownership and mode for the PHP user: chown -R www-data:www-data /var/lib/app/sessions && chmod 770 /var/lib/app/sessions","Verify from the app user's perspective: sudo -u www-data test -w /var/lib/app/sessions, or php -r 'var_dump(is_writable($dir));'","Use a directory guaranteed writable for local work, e.g. sys_get_temp_dir()","Check open_basedir in php.ini / pool config covers the chosen path"],"exampleFix":"// before\nnew Stream(['savePath' => '/var/lib/myapp/sessions']); // SavePathUnavailable: not writable\n\n// after (dir owned by the FPM user, writable)\n// shell: chown www-data:www-data /var/lib/myapp/sessions && chmod 770 /var/lib/myapp/sessions\nnew Stream(['savePath' => '/var/lib/myapp/sessions']);","handlingStrategy":"validation","validationCode":"$path = $options['savePath'] ?? ini_get('session.save_path');\nif (!is_dir($path)) {\n    @mkdir($path, 0770, true);\n}\nif (!is_writable($path)) {\n    throw new RuntimeException(\"Session save path {$path} is not writable by \" . get_current_user());\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Provision the session directory in deployment scripts with correct owner and mode","After switching PHP SAPI or user, re-run an is_writable() check on the session directory","Keep open_basedir in sync with the session path in hardened environments"],"tags":["php","phalcon","session","filesystem","permissions"],"backgroundTag":"path-not-writable","analyzedSha":"b7419de9cd0a8a3f48441ead84c9f8415d463e25","analyzedAt":"2026-08-21T06:21:18.811Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}