Set safe permissions for an Apache document root
Keep application code owned by the deployment account, give Apache read access, and grant write access only where the application needs it.
Keep site code owned by root or a deployment account. Apache should normally be able to read it, but should only be able to write to specific upload, cache, or runtime directories. Giving the web server ownership of the entire application lets a compromised application rewrite its own code.
The examples use Debian and Ubuntu's www-data account. Check the actual Apache user and, when relevant, the PHP-FPM pool user before applying them.
Establish a read-only baseline for Apache
For a site containing ordinary, nonsecret web content:
sudo chown -R root:www-data /var/www/mysite
sudo find /var/www/mysite -type d -exec chmod 755 {} \;
sudo find /var/www/mysite -type f -exec chmod 644 {} \;
Directories need execute permission to allow traversal. These modes allow everyone on the machine to read the content, not just the www-data group. Use 750 for directories and 640 for files when only the owner and web-server group need access. Keep secrets outside the document root and limit their permissions separately.
Do not blindly reset permissions on a tree containing executables, symlinks, or application-specific files. Review those requirements first. Every parent directory must also permit the web-server account to traverse the path.
Grant writes to a specific runtime directory
sudo chown -R www-data:www-data /var/www/mysite/uploads
sudo find /var/www/mysite/uploads -type d -exec chmod 750 {} \;
sudo find /var/www/mysite/uploads -type f -exec chmod 640 {} \;
Use the account that actually writes these files; PHP-FPM may run as a different user. Prefer storing uploads outside the document root and serving them through a controlled application path. If uploads must be directly accessible, prevent their execution through every configured handler and disallow per-directory overrides. Disabling PHP with a mod_php directive does not disable PHP-FPM, CGI, or other handlers. Test that uploaded scripts cannot execute and that sensitive source is not exposed.
Support several deployment users
A deployment group can own the writable source tree, while an ACL gives the runtime account read access:
sudo groupadd deploy
sudo usermod -aG deploy alice
sudo usermod -aG deploy bob
sudo chown -R root:deploy /var/www/mysite
sudo find /var/www/mysite -type d -exec chmod 2770 {} \;
sudo find /var/www/mysite -type f -exec chmod 660 {} \;
sudo setfacl -R -m u:www-data:r-X /var/www/mysite
sudo find /var/www/mysite -type d -exec setfacl -m d:u:www-data:r-X {} \;
Create the group only if it does not exist; users need a new login session for changed membership. Setgid preserves the group on new children, while default ACLs provide inherited runtime access. Verify the effective ACL mask with getfacl: deployment tools that replace directories or explicitly set modes can change the outcome. Reapply the narrower upload policy separately.
Check the actual failure
After deployment, test static reads and application writes independently. For Permission denied, inspect the logged path, all parent directories, the runtime user, and recent deployment changes. On SELinux systems, also verify persistent contexts: ordinary content commonly uses httpd_sys_content_t, while approved writable content uses httpd_sys_rw_content_t. Manage these with semanage fcontext and restorecon according to the distribution policy.
chmod -R 777 hides ownership problems and gives every local user write access. Fix the specific ownership, ACL, or security-label mismatch instead.
References
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan