Skip to main content

Cloudsoft Zone Build, deploy and scale with practical guides to PHP, Node.js, React, DevOps and AI automation. Explore the guides

Cloudsoft Zone
DevOpsLaravelPHPTroubleshooting guide

Laravel storage permissions on nginx: diagnose the runtime user before changing access

Fix Laravel storage permissions by identifying the real PHP-FPM and worker accounts, tracing parent directories and granting only required runtime access.

Cloudsoft Zone

6 min read

Laravel artwork with storage permission diagnosis: identify runtime users, trace directories and grant narrow write access.
Image: Cloudsoft Zone layout; Laravel / Public domain
In this article

A Laravel page returns a filesystem error, so somebody makes the whole project writable. The error disappears, but a compromised web process can now change application code. The correct fix starts with the failed path and the identity that tried to write it.

This Laravel storage permissions guide diagnoses nginx and PHP-FPM deployments without broad ownership changes. We will separate the web, worker and deploy accounts, inspect path traversal and ACLs, then verify the required writes and protected paths. It applies to a Linux filesystem; container mounts and managed object storage add different boundaries.

Use the actual application paths and runtime accounts in every example. Our Laravel production checklist explains the surrounding release setup. Do not change another application or the entire server's web directory while following this guide.

Laravel filesystem access depends on the deployment account, PHP-FPM pool user and separate queue or scheduler identity.
Diagnose the failing process rather than changing ownership to whichever account is easiest.

Step 1: Capture the exact path and operation

Record whether the application tried to create a directory, create a file, append to a log, replace a cached file or read an existing object. These operations require different access. Preserve a sanitized error and the current release identifier.

Inspect the final resolved path. A symlinked storage directory can lead to a shared location with different ownership, and a new release's bootstrap cache can have fresh permissions. Diagnose the target rather than only the visible symlink.

Do not delete uploaded files or empty runtime directories as a permission fix. A file can be customer data even when its directory is named storage. Separate deliberate cache rebuilds from retained uploads and documents.

Step 2: Identify the real process account

nginx passes PHP execution to PHP-FPM. The pool's worker account usually performs the application write, while nginx needs access to public static files and the FPM listener. A root-owned nginx master process does not mean PHP requests write as root.

Shell
ps -eo user,group,pid,args | grep '[p]hp-fpm'
ps -eo user,group,pid,args | grep '[a]rtisan queue:work'
id app-runtime

Substitute the intended runtime account and inspect the relevant pool configuration. Record the queue and scheduler accounts too. A deployment command run as root can create a cache or log file the web account cannot later update.

Process Typical access need
Deploy account Build artifacts and activate a release
PHP-FPM worker Read code; write selected runtime directories
Queue worker Read compatible code; write its required state
nginx worker Read public assets and connect to the PHP listener

The runtime need not be the owner of every file. A group policy or named ACL can allow narrow access while keeping code owned by the deploy account.

Step 3: Inspect the entire path

Use namei to inspect each component, then inspect access-control lists on the relevant target:

Shell
namei -l /srv/example-app/current/storage/logs
getfacl /srv/example-app/shared/storage/logs
getfacl /srv/example-app/current/bootstrap/cache

A process needs search permission on every parent directory to reach the target. A writable logs directory cannot compensate for an inaccessible parent. On a directory, write and search permissions govern creating or removing entries; updating an existing file also depends on that file's access.

Check ACL effective permissions as well as the named entry. An ACL mask can reduce the effective permissions of named users or groups. Record the existing ACL before changing it so you can restore the exact reviewed policy if necessary.

Effective storage access depends on parent traversal, target permissions and additional ACL, mount or security restrictions.
An apparent rwx ACL entry may be limited by its effective mask.

Step 4: Define the narrow writable set

Laravel commonly needs writable storage and bootstrap cache paths, but your application's actual files and deployment design determine the complete set. Persistent storage and per-release caches have different lifecycles.

Keep the application root, dependency code and production configuration protected from runtime modification where possible. Public uploaded files need deliberate serving rules; private documents must remain outside public access even when their storage directory is writable.

For a named-user ACL strategy, grant access on one reviewed runtime directory first:

Shell
sudo setfacl -m u:app-runtime:rwx /srv/example-app/shared/storage/logs
sudo setfacl -m d:u:app-runtime:rwx /srv/example-app/shared/storage/logs
getfacl /srv/example-app/shared/storage/logs

The first command changes access on that directory. The default entry governs new children created inside it. These commands do not repair all existing log files or unrelated subdirectories. Inspect those specific targets and grant only the required rights.

Review the effective mask after a change: adjusting a mask can affect other ACL entries too. If the deployment uses a group-and-setgid model instead, use that existing owner consistently rather than adding a competing ACL convention.

Step 5: Check new-file behavior

A permission fix that works once can fail after the next log rotation or deployment. Record who creates new files, the process umask, default ACL and resulting owner. The CLI, queue and PHP-FPM processes may create files differently.

Default ACLs do not guarantee every application-created file is readable or writable by every intended process. Creation mode, inherited ACL and effective mask interact. Inspect a harmless new file from the actual application workflow and confirm the relevant processes can use it.

After changing group membership, existing processes may still use their old group list. Replace the affected application processes through their documented supervisor; do not restart unrelated services or kill every PHP process on the host.

Step 6: Investigate non-permission restrictions

If the user and ACL appear correct, check whether the filesystem or container mount is read-only, whether disk space or inodes are exhausted and whether an enforced security profile denied the operation.

AppArmor restrictions are separate from Unix mode bits. Review the relevant denial evidence and amend only the application's required policy. Do not disable the host's security controls as a permanent workaround.

In a container, inspect the user identifier and mounted storage ownership on both sides. A name such as app inside the container does not guarantee the host uses the same numeric user identifier. An object-storage access denial is an IAM or credential policy issue, not a reason to change local file permissions.

Step 7: Verify access under the intended identity

Begin with non-writing checks for the selected directory:

Shell
sudo -u app-runtime test -x /srv/example-app/shared/storage/logs
sudo -u app-runtime test -w /srv/example-app/shared/storage/logs

Then exercise one harmless application write using its normal path. Confirm the error is gone, the created file has the intended access and the other required runtime account can use it. Shell checks alone do not prove an application security profile or mount allows the operation.

A storage permission fix verifies required writes, denial on protected code and suitable permissions for newly created files.
Default directory ACLs affect new children, not every existing file automatically.

Also verify denial on protected code and configuration. Do not actually modify those files to test denial; inspect effective access and the relevant user policy. A successful upload is insufficient evidence if the fix also made the entire codebase writable.

Step 8: Make the policy survive deployment

Document ownership, group or ACL rules as part of release creation and shared-storage provisioning. Preserve them when rotating logs or restoring uploads. Keep backup restoration from silently changing the runtime's access contract.

When rolling back a permission change, restore the recorded exact ACL or ownership policy for the reviewed targets. Avoid a second broad recursive command that guesses what the old state might have been.

Record the failed path, responsible account, corrected access and verification result. For a broader launch check, use the Laravel deployment checklist. For a reviewed server and storage setup, contact Cloudsoft Zone.

Featured image: Cloudsoft Zone technical layout with laravel artwork by Laravel (Public domain). Product mark resized proportionally for identification; no endorsement implied. Layout: CC BY-SA 4.0.

Frequently asked questions

  • Should I set the Laravel directory to chmod 777?

    No. Identify the actual writer and grant required access to specific runtime paths. World-writable code or configuration introduces an unnecessary compromise path.

  • Why does CLI succeed while the web request cannot write?

    The CLI and PHP-FPM commonly run as different users, with different group memberships, configuration and security policies. Check the FPM pool user and the full path.

  • Do default ACLs repair permissions on existing files?

    No. They guide inheritance for newly created children. Inspect and deliberately repair the specific existing files that require access.

Advertise on CloudSoftZone

Your next customer could start right here.

Introduce your tool, product or business to readers exploring technology.

Advertise with us

Explore placements and request a private quote

Sources

  1. Laravel deployment write requirements — Laravel
  2. PHP-FPM pool identity — PHP
  3. namei path inspection — Ubuntu
  4. getfacl access inspection — Ubuntu
  5. setfacl masks and inheritance — Ubuntu
  6. PHP umask behavior — PHP
  7. Ubuntu AppArmor — Ubuntu

Comments

No comments yet. Be the first to join the conversation.

Leave a comment

Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.

Never published. Used only to contact you about this comment.

Loading the security check…

Comments are moderated before publication.