Linux9 min readPublished: 4 Nov 2024Updated: 29 Jul 2026

Linux File Permissions for Web Servers and WordPress

The correct file and folder permissions for a Linux web server, why 777 breaks security, and the commands to reset a WordPress install without breaking it.

G7Cloud Engineering
Platform team
Share:
Abstract illustration representing linux file permissions for web servers and wordpress.
  • 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.

WhatOctalLooks likeWhy
Directories755drwxr-xr-xOwner can change contents, everyone else can enter and list
Files644-rw-r--r--Owner can edit, the web server can read
wp-config.php640-rw-r-----Contains database credentials: group only, never world-readable
Uploads directory775drwxrwxr-xThe web server group needs to write here
Shell scripts750-rwxr-x---Executable by owner and group, invisible to everyone else
SSH private key600-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.

Reading ls -l output
$ 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 directory
Columns three and four are the owner and the group. Those two matter as much as the permission bits themselves.

The 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.

NumberPermissionsMeaning
7rwxRead, write, execute
6rw-Read and write
5r-xRead and execute (the normal setting for a directory others must enter)
4r--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.

Set ownership correctly
# 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 deploy
Replace www-data with apache or nginx on RHEL-family systems. See adding a user to a group for the details of group membership.

Group 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.

PathPermissionsOwner:groupNotes
All directories755deploy:www-dataThe default for everything
All files644deploy:www-dataThe default for everything
wp-config.php640deploy:www-dataDatabase credentials live here
wp-content/uploads775deploy:www-dataMedia library writes here
wp-content/cache775deploy:www-dataOnly if a caching plugin needs it
.htaccess644deploy:www-data664 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.

The full reset, in order
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/uploads
Step 5's g+s is the setgid bit: new files created inside uploads inherit the www-data group automatically, so this stays fixed.
Pro Tip

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.

SymptomUsual causeCheck
403 Forbidden on every pageA parent directory is missing the execute bitnamei -l /var/www/example.com/index.php
Blank page or 500 errorPHP cannot read a required fileCheck the PHP error log for "failed to open stream: Permission denied"
Media uploads failThe web server cannot write to wp-content/uploadssudo -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.

Check the whole path, not just the file
# 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'
A file can be 644 and still unreadable if any directory above it denies entry. namei finds that in one command.

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.
A document root that stays consistent
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 {} \;
The leading 2 in 2775 is the setgid bit. Every new directory created underneath inherits it, so the arrangement holds over time.

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.

Put this into practice on G7Cloud

Every site runs in its own dedicated container behind ScaleShield, with daily backups that are restore-tested every night. Start on the free plan, no card needed.

About G7Cloud Engineering

Platform team

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 →