Table of contents of the article:
In the world of web hosting there is one of those “truths” repeated so many times that it has almost become dogma: directories must have permissions 755 and files must have permissions 644Guides, tutorials, control panels, general-purpose providers, support documentation, forums, and even some automatic permission restoration tools say so. The problem is that this rule, while convenient and often effective, isn't necessarily the right one from a security perspective.
The point isn't to argue that a site with 755 directories and 644 files is automatically compromised or absolutely insecure. That would be a trivial simplification, and in a certain sense, even false. The point is different: 755 and 644 are permissions designed to ensure universal compatibility, not to truly respect the principle of least privilege.And this is where the misunderstanding arises, or rather, the original sin of much of the commercial hosting based on control panels like cPanel, Plesk, and the like.
In a multi-user Linux system, the third permissions digit, the one referring to "others," meaning all other system users, should not be used as a shortcut to operate the web server. If the web server needs to read a website's files, then it should be able to do so because it belongs to the correct group, because the PHP process is running under the correct user, or because the service architecture has been thoughtfully designed. Not because we've decided to make the website readable by any local user on the system.
The real meaning of 755 and 644
Let's start from the basics. A permit 755 on a directory means:
rwxr-xr-x
The owner can read, write, and enter the directory. The group can read and enter. Everyone else can read and enter. On a directory, the execute bit doesn't mean "run a program," but traverse the directory, that is, being able to enter it, reach files and subdirectories and resolve the path.
A permit 644 on a file means:
rw-r--r--
The owner can read and write. The group can read. Everyone else can read. So, if a PHP file, configuration file, or text file has permissions of 644, any local user who isn't the owner and isn't a member of the group can still read it, as long as they have access to the parent path.
Here, many fall into the misconception that "you can't read the PHP source file via the web." True, if the web server is configured correctly. But Linux permissions govern more than just HTTP access. They also govern local access from shells, system processes, compromised scripts, poorly restricted users, cron jobs, backup tools, FTP or SFTP accounts, other sites hosted on the same machine, and any process that manages to run under a different local user.
Saying that 755 and 644 are “fine” often ignores the multi-user context in which those permissions are applied.
Why do panels still use 755 and 644?
The answer is uncomfortable but simple: because they almost always workControl panels are general-purpose products. They must support both old and new stacks, well-configured and poorly configured servers, Apache with mod_php, Apache with PHP-FPM, Nginx as a reverse proxy, LiteSpeed, LSAPI, suPHP, suexec, FastCGI, FTP users, web file managers, backups, restores, migrations, WordPress plugins, automated installers, old content management systems, poorly written scripts, and clients that upload files with dubious permissions.
In this scenario, 755 for directories and 644 for files is the easiest way to avoid support tickets. If the web process does not belong to the correct group, if Apache runs as nobody, if Nginx runs as nginx, if the PHP-FPM pool is not perfectly aligned with the site user, if the File Manager still needs to display and manipulate files, those permissions guarantee one thing: the site will probably remain responsive.
But that doesn't make them the best permits. It makes permissions more convenient for the panel vendor and the provider who wants to reduce compatibility issues. It's a huge difference.
When a panel automatically sets directories to 755 and files to 644, it's implicitly saying, "I'm not sure which user or group should read these files, so I'll allow others to read them too." It's a pragmatic choice. But from a systems perspective, it's a shortcut.
The principle of least privilege
The principle of least privilege dictates that each user, process, or service should have only the permissions strictly necessary to perform its function. Applied to website files, this means:
- the user who owns the site must be able to read and write the files;
- the PHP process must be able to read PHP files and possibly write only to the designated directories;
- the web server must be able to read the static assets it needs to serve directly;
- other users of the system should not be able to read anything;
- Sensitive files should not be readable by unnecessary processes.
If we take this principle seriously, the 755/644 pair immediately appears too large. The most coherent configuration instead becomes:
Directory: 750 File: 640
Or, in some even more restrictive cases:
Directory: 750 o 700 File: 640 o 600
For particularly sensitive files such as wp-config.php, .env, configuration.php of Joomla or app/etc/env.php For Magento, it may make sense to go further, depending on how PHP accesses the file.
WordPress, PHP-FPM, and Nginx: Why the Model Is Changing
A significant part of the confusion arises from the fact that many historical recommendations derive from traditional Apache environments, often with mod_php or configurations where the web server needed to read much of the content directly. But In the modern world, especially in professional environments, a WordPress or PHP site is often run under a different model:
- Nginx handles the HTTP connection and serves static assets;
- PHP-FPM runs PHP code;
- each PHP-FPM pool can run as a site-specific user;
- the PHP code is not “read by Nginx” to be interpreted, but passed to PHP-FPM via FastCGI;
- PHP process can be isolated per user, domain or subscription.
In this scenario, continuing to think as if everything had to be readable by “others” is technically backward. If PHP-FPM is running as the site user, PHP files can be read by the owner user. If Nginx needs to serve static files, it can access those files via the group. There's no logical need to grant read permission to the rest of the local world.
The correct model is to design ownership and groups consistently. For example:
owner: utente_sito group: web-utente_sito directory: 750 file: 640
In this scheme, the site user maintains control of the files, while the group grants access to authorized processes, such as Nginx or a specific service. Everyone else has no access.
The problem of nginx being used globally
A seemingly simple solution might be: "put Nginx in the user's group" or "assign the files to the nginx group." This reasoning is correct in its direction, but it must be applied carefully.
A crude example might be:
chown -R nomesito:nginx /home/nomesito/public_html
find /home/nomesito/public_html -type d -exec chmod 750 {} ;
find /home/nomesito/public_html -type f -exec chmod 640 {} ;
This works: Nginx, being in the group, can traverse directories and read files. "others" users can't do anything. However, if all sites on the server use the global group, nginx, then any process running as Nginx can potentially read files from all those sites. In a single-site server, this may be acceptable; in a multi-user environment, it's not the best isolation.
The most elegant solution is to use dedicated groups per site or per application context:
groupadd web-nomesito
usermod -aG web-nomesito nginx
usermod -aG web-nomesito nomesito
chown -R nomesito:web-nomesito /home/nomesito/public_html
find /home/nomesito/public_html -type d -exec chmod 750 {} ;
find /home/nomesito/public_html -type f -exec chmod 640 {} ;
To keep the group consistent on new files created inside directories, you can also use the bit setgid on directories:
find /home/nomesito/public_html -type d -exec chmod g+s {} ;
This way, new files created within those directories inherit the parent directory's group. It's not a magic wand, because you still have to manage umasks, deployments, SFTP, PHP processes, and update tools, but it's a much more sensible model than opening everything up to "others."
Why 750/640 is more correct
With directories at 750 and files at 640 you get a much cleaner separation:
rwxr-x--- directory rw-r----- file
The owner has full control. The group can read and pass through. Others can't do anything. This means that another local user cannot snoop around in the site's files just because the control panel has decided to use permissive permissions to avoid problems.
In WordPress, the difference isn't trivial. Within a WordPress installation, we can find configuration files, database credentials, authentication keys, custom plugins, forgotten backups, exports, logs, SQL dumps, temporary files, caches, external service configurations, API tokens, SMTP credentials, and much more. Of course, many of these files shouldn't be in the document root. But we know how the real world works: sites accumulate material, plugins, temporary folders, and migration leftovers.
If everything is readable by others, any weakness in local isolation can translate into a loss of confidentiality. A spectacular remote vulnerability isn't necessarily necessary. Sometimes a compromised account on the same server, a poorly isolated script, a cron job running with inadequate privileges, or a superficial multiuser setup are all it takes.
A more correct configuration for WordPress
On a modern stack with Nginx and PHP-FPM per user, a more sensible setup might be this:
SITE_USER="nomesito"
SITE_GROUP="web-nomesito"
SITE_PATH="/home/nomesito/public_html"
groupadd -f "$SITE_GROUP"
usermod -aG "$SITE_GROUP" nginx
usermod -aG "$SITE_GROUP" "$SITE_USER"
chown -R "$SITE_USER:$SITE_GROUP" "$SITE_PATH"
find "$SITE_PATH" -type d -exec chmod 750 {} ;
find "$SITE_PATH" -type f -exec chmod 640 {} ;
find "$SITE_PATH" -type d -exec chmod g+s {} ;
chmod 600 "$SITE_PATH/wp-config.php"
Of course, this is a conceptual example, not a recipe to blindly launch on any server. First, you need to check how PHP-FPM runs, what user it's running under, what primary group it uses, how files are created by WordPress updates, how the deployment system works, and whether ACLs, SELinux, AppArmor, CageFS, a control panel, or other isolation mechanisms are in place.
In some cases, 750/640 may be more suitable. In others, 750/650 may be more suitable if you want to maintain the execution bit on specific files or if there are particular operational needs. In still others, 700/600 may be more suitable for sections not served directly from the web. The point is not to idolize a number, but to stop considering 755/644 as a universal law.
The responsibility of control panels
Plesk and cPanel aren't "wrong" because they don't understand Linux permissions. They're mature, complex products used in vastly different contexts. Their problem is another: they need to choose defaults that work in as many scenarios as possible. And when software has to choose between rigorous security and reducing support issues, it often chooses the latter.
The result is that a setting designed for compatibility becomes, in the common perception, a security recommendation. And that's dangerous. Because the inexperienced system administrator reads "755 directories, 644 files" and thinks, "That's the correct configuration." What he should actually read is, "This is the configuration that will probably avoid 403 errors in most general-purpose environments."
The difference is substantial. A default value isn't automatically a best practice. A value that avoids tickets isn't automatically the safest value. A configuration tolerated by a control panel isn't necessarily the ideal configuration for a modern, expertly designed, managed environment.
The case of writable files
Another often confusing topic is writeability. WordPress, plugins, caching, uploads, and updates all need to write to certain directories. But this doesn't mean the entire site needs to be writable or readable by everyone.
More rigorous management should distinguish between:
- application code, which should be modifiable only by the owner or the deployment system;
- upload directories, which must be writable by the PHP process;
- cache directories, which must be writable by the application process;
- configuration files, which should be as restrictive as possible;
- static assets, which Nginx should be able to read but not modify.
A mature policy doesn't say "all 755s and 644s." It says: "Who should read? Who should write? Who should pass through? Who should have no access?" Only then do you choose chmod, chown, groups, ACLs, and umasks.
Conclusion: the problem is not Linux, it's the laziness of the default
Linux already offers all the tools to do things well: users, groups, permissions, ACLs, umask, setgid, process isolation, PHP-FPM separate pools, chroot, containers, namespaces, MAC systems like SELinux and AppArmor. The problem isn't the Unix model. The problem is the habit of using loose configurations because "that's how it works."
Control panels have historically promoted or standardized permissions like 755 and 644 because they're simple, compatible, and reduce the risk of breaking poorly configured sites. But in a professional environment, especially for WordPress and more generally for PHP sites served by Nginx with a PHP-FPM backend, there are more correct, more consistent, and more respectful configurations that respect the principle of least privilege.
750 directories and 640 files, with properly designed ownership and groups, represent a healthier model. It's not always straightforward to implement, it's not always compatible with any panel without modifications, but it's conceptually superior. The web server shouldn't read because "everyone can read." It should read because it's authorized to do so. PHP shouldn't write everywhere. It should write only where needed. Other system users shouldn't be able to snoop around the code and configurations of a site that doesn't belong to them.
True system security isn't about copying commands from a generic guide, but understanding the execution model of your stack. If the server is well designed, 755 and 644 aren't a necessity: they're just a compromise inherited from the past. And like many historical compromises in shared hosting, they persist not because they're right, but because they're convenient.