- Directories should be 755, files 644. That is the correct answer for almost every web server on almost every day.
- wp-config.php holds your database password and should be 640, readable by the web server group and no other account.
- Files should be owned by your deploy user, with the web server user in the group. The web server does not need to own your code.
- 777 gives every account on the server permission to rewrite your site. It is never the fix, it is a way of hiding the actual problem.
- Only the uploads directory needs to be writable by the web server on a normal WordPress site.
What permissions should a web server use?
Directories 755, files 644. Directories need the execute bit so they can be entered; files only need to be read. The web server should never need write access to your application code, only to the specific directories that store uploads, cache and logs. Anything wider than that is a security cost with no benefit.
| What | Octal | Looks like | Why |
|---|---|---|---|
| Directories | 755 | drwxr-xr-x | Owner can change contents, everyone else can enter and list |
| Files | 644 | -rw-r--r-- | Owner can edit, the web server can read |
| wp-config.php | 640 | -rw-r----- | Contains database credentials: group only, never world-readable |
| Uploads directory | 775 | drwxrwxr-x | The web server group needs to write here |
| Shell scripts | 750 | -rwxr-x--- | Executable by owner and group, invisible to everyone else |
| SSH private key | 600 | -rw------- | Owner only. SSH refuses to use anything looser |
Memorise the first two rows. The rest are the exceptions and each one has a specific reason.
777 is not a fix
chmod 777 means every account on the server can read, edit and execute the file. On a shared machine that includes accounts you do not control. If a plugin only works at 777, the actual problem is ownership, and the fix is chown, not chmod.
How do Linux file permissions actually work?
Every file carries three sets of three permissions: what the owner can do, what the owning group can do, and what everybody else can do. Each set has read, write and execute. Run ls -l and the first ten characters spell it out.
$ ls -l /var/www/example.com
-rw-r--r-- 1 deploy www-data 405 Jul 29 09:14 index.php
drwxr-xr-x 4 deploy www-data 4096 Jul 29 09:14 wp-content
-rw-r--r--
|[-][-][-]
| | | +-- everyone else: read only
| | +----- group (www-data): read only
| +-------- owner (deploy): read and write
+---------- file type: - is a file, d is a directoryThe numeric form adds the bits together: read is 4, write is 2, execute is 1. So 6 is read plus write, 7 is all three, 5 is read plus execute. Three digits, one for each set.
| Number | Permissions | Meaning |
|---|---|---|
| 7 | rwx | Read, write, execute |
| 6 | rw- | Read and write |
| 5 | r-x | Read and execute (the normal setting for a directory others must enter) |
| 4 | r-- | Read only |
| 0 | --- | No access |
755 reads as: owner 7, group 5, everyone else 5.
Execute means something different on a directory
On a file, execute means you can run it. On a directory it means you can enter it and access things inside. A directory with 644 cannot be entered at all, which is why removing the execute bit from a folder appears to delete a whole site.
Which user should own my website files?
Not the web server. This is the single most common mistake. On Debian and Ubuntu the web server runs as www-data; on AlmaLinux, Rocky and CentOS it runs as apache or nginx. If that account owns your files, then any code the web server executes can rewrite any file on the site, which is exactly what an attacker who lands a webshell wants.
The safer arrangement is a separate deploy user owning the files, with the web server user placed in the group. The web server then reads everything and writes only where you have explicitly granted group write.
# Find out which user your web server actually runs as
ps aux | grep -E '[a]pache2|[n]ginx|[p]hp-fpm' | awk '{print $1}' | sort -u
# Own the files as the deploy user, group them to the web server
sudo chown -R deploy:www-data /var/www/example.com
# Add the deploy user to the web server group so both can collaborate
sudo usermod -aG www-data deployGroup changes need a new login
Adding yourself to a group does not affect the shell you are already in. Log out and back in, or run newgrp www-data, or you will spend twenty minutes confused about why the permission you just granted has no effect.
What are the correct WordPress file permissions?
WordPress follows the general rule with two exceptions: wp-config.php should be tighter than everything else because it holds your database password, and wp-content/uploads must be writable by the web server because that is where media lands.
| Path | Permissions | Owner:group | Notes |
|---|---|---|---|
| All directories | 755 | deploy:www-data | The default for everything |
| All files | 644 | deploy:www-data | The default for everything |
| wp-config.php | 640 | deploy:www-data | Database credentials live here |
| wp-content/uploads | 775 | deploy:www-data | Media library writes here |
| wp-content/cache | 775 | deploy:www-data | Only if a caching plugin needs it |
| .htaccess | 644 | deploy:www-data | 664 only while you are editing permalinks |
Everything not in this table gets 755 for directories and 644 for files.
Plugin and core updates without write access
If the web server cannot write to wp-content/plugins, WordPress will ask for FTP details when you click update. The better answer is to update from the command line with WP-CLI, or let your host do it, rather than opening up write access to your whole plugin directory.
How do I reset broken WordPress permissions safely?
Never run chmod -R 755 across a site: that sets the execute bit on every file, including images and PHP source. Use find to apply directory and file rules separately. Run the commands in this order and nothing will be left in a broken state.
cd /var/www/example.com
# 1. Ownership first: deploy user owns, web server group reads
sudo chown -R deploy:www-data .
# 2. Directories to 755
sudo find . -type d -exec chmod 755 {} \;
# 3. Files to 644
sudo find . -type f -exec chmod 644 {} \;
# 4. Lock down the config file
sudo chmod 640 wp-config.php
# 5. Give the web server group write access to uploads only
sudo chmod -R 775 wp-content/uploads
sudo chmod g+s wp-content/uploadsg+s is the setgid bit: new files created inside uploads inherit the www-data group automatically, so this stays fixed.On a large site, find -exec spawns one process per file and can take minutes. find . -type f -print0 | xargs -0 chmod 644 does the same work in a fraction of the time.
Before you run any of this on a live site, take a backup you have actually tested restoring. A permissions reset is low risk, but it is applied recursively and there is no undo. Our backup strategy guide covers what a usable backup looks like.
How do I diagnose a permission problem?
Work from the symptom. Three symptoms cover nearly everything, and each points at a different cause.
| Symptom | Usual cause | Check |
|---|---|---|
| 403 Forbidden on every page | A parent directory is missing the execute bit | namei -l /var/www/example.com/index.php |
| Blank page or 500 error | PHP cannot read a required file | Check the PHP error log for "failed to open stream: Permission denied" |
| Media uploads fail | The web server cannot write to wp-content/uploads | sudo -u www-data test -w wp-content/uploads && echo writable |
namei is the tool people forget: it walks every directory in a path and prints the permissions at each level.
# Shows permissions at every level of the path
namei -l /var/www/example.com/wp-content/uploads/2026/07
# Test as the web server user itself, which is the definitive answer
sudo -u www-data test -r /var/www/example.com/index.php && echo 'can read' || echo 'cannot read'
sudo -u www-data test -w /var/www/example.com/wp-content/uploads && echo 'can write' || echo 'cannot write'If the error log is where you need to look next, understanding Linux system logs covers where each service writes and how to follow it live.
How do permissions interact with SFTP and deployments?
Most permission drift comes from mixed access methods. You deploy over SSH as deploy, a colleague uploads over SFTP as webmaster, and a plugin writes as www-data. Three owners, three sets of permissions, and files the rest of the team cannot edit.
- Put every human in the same group and set that group on the document root, so anything anyone creates is editable by the rest of the team.
- Set the setgid bit on the document root with
chmod g+s, so new files and directories inherit the group instead of taking the creator's default. - Set a shared umask of 002 for the deploy accounts, which makes new files 664 and new directories 775 rather than 644 and 755.
- Pick one deployment method and stick to it. Mixing git pull, SFTP and the WordPress plugin installer on the same site is what produces the mess in the first place.
sudo groupadd webdev
sudo usermod -aG webdev deploy
sudo usermod -aG webdev webmaster
sudo chown -R deploy:webdev /var/www/example.com
sudo find /var/www/example.com -type d -exec chmod 2775 {} \;
sudo find /var/www/example.com -type f -exec chmod 664 {} \;Isolation removes most of this
On G7Cloud each site runs in its own container with its own filesystem and its own database, so there are no other accounts on the box to protect against and no shared PHP pool to leak between sites. The permission rules above still apply inside the container, but the blast radius of getting one wrong stops at that site. See how our WordPress hosting is built.
Frequently asked questions
What are the correct WordPress file permissions?
Directories 755, files 644, wp-config.php 640, and wp-content/uploads 775 so the web server group can write media. Files should be owned by your deploy user with the web server user (www-data, apache or nginx) as the group. Nothing on a normal WordPress site needs 777.
Is chmod 777 ever acceptable?
No. 777 lets every account on the server, including any process an attacker manages to run, rewrite the file. When a plugin only works at 777 the problem is ownership: the web server user is not in the owning group. Fix it with chown and a 775 group-write directory instead.
Should www-data own my WordPress files?
No. If the web server user owns the files, any code it executes can rewrite the entire site, which is exactly what a webshell needs. Own the files as a separate deploy user and put www-data in the group, granting group write only to uploads and cache.
Why do I get 403 Forbidden after fixing file permissions?
Almost always a directory above the file is missing its execute bit, so the web server cannot enter the path to reach it. Run namei -l against the full path: it prints permissions at every level and shows you exactly which directory is blocking.
What is the setgid bit and why should I use it?
setgid (chmod g+s, or a leading 2 in the octal form) makes new files and directories inherit the parent's group rather than the creating user's default group. On a shared document root it keeps ownership consistent, so permissions do not drift every time a different person uploads something.
How do I check permissions as the web server user?
Use sudo -u www-data test -r <path> for read and -w for write. That answers the question directly rather than making you infer it from the permission bits, and it accounts for group membership and parent directories at the same time.
About G7Cloud Engineering
Articles written by the engineers who build and run G7Cloud: UK managed hosting and the AI Website Builder. We write about what we operate every day: containers, backups, databases, and the small-business websites that run on them.
More about G7Cloud →