WordPress File Permissions: The Complete Security Guide
Learn how to set correct WordPress file permissions for maximum security. Covers chmod, directory permissions, wp-config.php hardening, .htaccess protection, and uploads directory risks.
Introduction: Why File Permissions Are a Critical Security Layer
File permissions are one of the most fundamental — and most frequently misconfigured — security controls on any WordPress installation. Every file and directory on your Linux server has a set of permissions that determine who can read, write, and execute it. When these permissions are too loose, attackers who gain even limited access to your server can escalate their privileges, inject malicious code, modify configuration files, or plant backdoors that survive plugin updates and even full WordPress reinstalls.
The problem is widespread. Shared hosting environments, one-click installers, FTP clients with default settings, and hurried deployments all contribute to WordPress sites running with permissions that are far more permissive than necessary. A single misconfigured directory — say, wp-content/uploads/ set to 777 — can be the entry point for a complete site takeover.
This guide covers everything you need to know about WordPress file permissions: what they mean, how to set them correctly, how to harden critical files like wp-config.php and .htaccess, and how to use automated tools like the VistoShield Security Scanner to continuously monitor your permissions and catch misconfigurations before attackers do.
Understanding Linux File Permissions
Before configuring WordPress-specific permissions, you need to understand how Linux file permissions work. Every file and directory on a Linux system has three permission sets assigned to three user categories.
The Three Permission Types
- Read (r = 4) — Allows viewing the contents of a file or listing the contents of a directory.
- Write (w = 2) — Allows modifying a file or creating/deleting files within a directory.
- Execute (x = 1) — Allows running a file as a program or entering a directory (traversing it).
The Three User Categories
- Owner (u) — The user who owns the file. Typically the web server user or your hosting account user.
- Group (g) — Users who belong to the file’s group. On many hosting setups, the web server shares a group with your account.
- Others (o) — Everyone else on the system. On shared hosting, this includes other customers’ accounts.
How Numeric Permissions Work
Permissions are expressed as a three-digit octal number. Each digit is the sum of the permission values for that user category:
# Permission breakdown for 755:
# Owner: 7 = 4(read) + 2(write) + 1(execute) = rwx
# Group: 5 = 4(read) + 0(no write) + 1(execute) = r-x
# Others: 5 = 4(read) + 0(no write) + 1(execute) = r-x
# Permission breakdown for 644:
# Owner: 6 = 4(read) + 2(write) + 0(no execute) = rw-
# Group: 4 = 4(read) + 0(no write) + 0(no execute) = r--
# Others: 4 = 4(read) + 0(no write) + 0(no execute) = r--
Viewing Current Permissions
Use the ls -la command to view permissions on your WordPress files:
$ ls -la /var/www/html/wordpress/
total 228
drwxr-xr-x 5 www-data www-data 4096 Mar 15 10:30 .
drwxr-xr-x 3 root root 4096 Mar 10 08:00 ..
-rw-r--r-- 1 www-data www-data 405 Feb 6 12:00 index.php
-rw-r--r-- 1 www-data www-data 19915 Mar 1 09:00 license.txt
-rw-r--r-- 1 www-data www-data 7399 Mar 1 09:00 readme.html
-rw-r--r-- 1 www-data www-data 7211 Mar 1 09:00 wp-activate.php
drwxr-xr-x 9 www-data www-data 4096 Mar 1 09:00 wp-admin
-rw-r--r-- 1 www-data www-data 351 Feb 6 12:00 wp-blog-header.php
-rw-r----- 1 www-data www-data 3200 Mar 10 14:22 wp-config.php
drwxr-xr-x 4 www-data www-data 4096 Mar 1 09:00 wp-content
drwxr-xr-x 30 www-data www-data 16384 Mar 1 09:00 wp-includes
Recommended WordPress File Permissions
WordPress.org and the broader security community agree on a standard set of permission recommendations. These balance the need for WordPress to function (it needs to read its own files and write to certain directories) with the principle of least privilege.
Standard Permission Table
| Path | Type | Recommended | Dangerous | Why |
|---|---|---|---|---|
/ (WordPress root) | Directory | 755 | 777 | Web server needs to traverse, not write |
wp-admin/ | Directory | 755 | 777 | Admin files should not be writable by others |
wp-includes/ | Directory | 755 | 777 | Core files must remain unmodified |
wp-content/ | Directory | 755 | 777 | Parent directory for plugins, themes, uploads |
wp-content/uploads/ | Directory | 755 | 777 | Must be writable by web server, not world-writable |
wp-content/plugins/ | Directory | 755 | 777 | Plugin code must not be modifiable by others |
wp-content/themes/ | Directory | 755 | 777 | Theme code must not be modifiable by others |
.htaccess | File | 644 | 666/777 | Controls URL rewriting and access rules |
wp-config.php | File | 640 or 600 | 644/666 | Contains database credentials and auth keys |
All other .php files | File | 644 | 666/777 | PHP files should never be world-writable |
Setting Permissions with chmod
Use these commands to set the correct permissions across your entire WordPress installation:
# Navigate to your WordPress root
cd /var/www/html/wordpress
# Set all directories to 755
find . -type d -exec chmod 755 {} \;
# Set all files to 644
find . -type f -exec chmod 644 {} \;
# Harden wp-config.php
chmod 640 wp-config.php
# Verify the changes
ls -la wp-config.php
# Expected output: -rw-r----- 1 www-data www-data 3200 Mar 10 14:22 wp-config.php
Setting Correct Ownership
Permissions alone are not sufficient. You also need to ensure that file ownership is correct. The owner should be the web server user (commonly www-data on Ubuntu/Debian or apache/nginx on AlmaLinux/CentOS):
# For Ubuntu/Debian with Apache
sudo chown -R www-data:www-data /var/www/html/wordpress
# For AlmaLinux/CentOS with Apache
sudo chown -R apache:apache /var/www/html/wordpress
# For Nginx
sudo chown -R nginx:nginx /var/www/html/wordpress
# Verify ownership
ls -la /var/www/html/wordpress/wp-config.php
Hardening wp-config.php
The wp-config.php file is the single most sensitive file in your WordPress installation. It contains your database credentials, authentication keys and salts, table prefix, debug settings, and other configuration that an attacker would love to read. Protecting this file is non-negotiable.
Step 1: Restrict Permissions
Set wp-config.php to 640 (owner can read/write, group can read, others have no access) or 600 (only owner can read/write) depending on your server setup:
# Recommended: 640 (works on most setups)
chmod 640 wp-config.php
# Maximum restriction: 600 (may cause issues on some shared hosts)
chmod 600 wp-config.php
Step 2: Move wp-config.php Above the Web Root
WordPress supports placing wp-config.php one directory above the WordPress installation directory. This means it is outside the web-accessible directory tree, so even if your web server is misconfigured and starts serving PHP files as plain text, wp-config.php remains inaccessible:
# If WordPress is installed at /var/www/html/wordpress/
# Move wp-config.php to /var/www/html/
mv /var/www/html/wordpress/wp-config.php /var/www/html/wp-config.php
# WordPress will automatically look one level up for wp-config.php
# No code changes needed
This technique works because WordPress’s wp-load.php checks for wp-config.php in its own directory first, and if not found, checks the parent directory. It is one of the simplest and most effective hardening measures available.
Step 3: Block Direct Access via .htaccess
Add the following rule to your .htaccess file to prevent any direct HTTP requests to wp-config.php:
# Protect wp-config.php from direct access
<Files wp-config.php>
Order Allow,Deny
Deny from all
</Files>
For Nginx, add this to your server block:
# Nginx: Block direct access to wp-config.php
location ~* wp-config\.php {
deny all;
return 404;
}
Step 4: Ensure Strong Authentication Keys
Your wp-config.php should contain unique authentication keys and salts. If they are set to the default placeholder values, replace them immediately using the WordPress.org salt generator:
# Generate new keys (copy the output into wp-config.php)
curl -s https://api.wordpress.org/secret-key/1.1/salt/
These keys are used to encrypt information stored in cookies and secure user sessions. Weak or default keys make session hijacking trivial.
Securing the .htaccess File
The .htaccess file controls URL rewriting, access rules, and various Apache directives. An attacker who can modify .htaccess can redirect all traffic to a phishing page, disable security headers, enable directory listing, or create rewrite rules that serve malware to visitors while showing normal content to administrators.
Recommended .htaccess Permissions
# Set .htaccess to 644
chmod 644 .htaccess
# If WordPress doesn't need to update .htaccess (no permalink changes planned):
chmod 444 .htaccess
Protecting .htaccess from Direct Access
# Add this to .htaccess to protect itself
<Files .htaccess>
Order Allow,Deny
Deny from all
</Files>
# Also protect other sensitive dot-files
<FilesMatch "^\.(?!well-known)">
Order Allow,Deny
Deny from all
</FilesMatch>
Essential Security Directives for .htaccess
Beyond permissions, your .htaccess file should include these security directives:
# Disable directory browsing
Options -Indexes
# Disable server signature
ServerSignature Off
# Protect wp-includes directory
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>
# Block access to sensitive files
<FilesMatch "(^#.*#|\.(bak|conf|dist|fla|in[ci]|log|orig|psd|sh|sql|sw[op])|~)$">
Order Allow,Deny
Deny from all
Satisfy All
</FilesMatch>
The Uploads Directory: A Common Attack Vector
The wp-content/uploads/ directory is arguably the most dangerous directory in a WordPress installation. It is the one directory that must be writable by the web server (to allow media uploads), and it is the directory most commonly targeted by attackers to upload malicious PHP files.
Why the Uploads Directory Is Dangerous
The attack scenario is straightforward:
- An attacker exploits a vulnerability in a plugin (e.g., a file upload form that doesn’t properly validate file types)
- They upload a PHP file disguised as an image (e.g.,
shell.php.jpgorpayload.php) towp-content/uploads/ - They access the file directly via the browser:
https://yoursite.com/wp-content/uploads/2026/03/payload.php - The PHP file executes, giving them a web shell with full server access
Preventing PHP Execution in Uploads
Create a .htaccess file inside wp-content/uploads/ with the following content:
# wp-content/uploads/.htaccess
# Prevent PHP execution in the uploads directory
# Method 1: Deny all PHP files
<Files "*.php">
Order Allow,Deny
Deny from all
</Files>
# Method 2: More comprehensive - blocks multiple PHP extensions
<FilesMatch "\.(php|php5|php7|phtml|pht|phps|cgi|pl|py|sh|bash)$">
Order Allow,Deny
Deny from all
</FilesMatch>
# Method 3: Disable PHP engine entirely (if mod_php is used)
<IfModule mod_php.c>
php_flag engine off
</IfModule>
<IfModule mod_php7.c>
php_flag engine off
</IfModule>
<IfModule mod_php8.c>
php_flag engine off
</IfModule>
For Nginx, add this to your server block:
# Nginx: Prevent PHP execution in uploads
location ~* /wp-content/uploads/.*\.php$ {
deny all;
return 403;
}
Additional Uploads Hardening
- Never set uploads to 777. Use
755for directories and644for files within uploads. - Disable directory listing within uploads to prevent attackers from browsing uploaded files.
- Add an empty
index.phpto the uploads directory:<?php // Silence is golden. - Monitor uploads for unexpected file types — PHP, JavaScript, and HTML files should never appear in the uploads directory.
wp-content Directory Hardening
Beyond the uploads directory, the broader wp-content/ directory tree requires attention. This directory contains your plugins, themes, and any custom code — all of which are potential targets.
Protecting Plugin and Theme Directories
# Set plugin and theme directories to 755
chmod 755 wp-content/plugins
chmod 755 wp-content/themes
# Set individual plugin/theme files to 644
find wp-content/plugins -type f -exec chmod 644 {} \;
find wp-content/themes -type f -exec chmod 644 {} \;
Disabling the WordPress File Editor
WordPress includes a built-in file editor (Appearance > Theme File Editor, Plugins > Plugin File Editor) that allows administrators to modify theme and plugin PHP files directly from the dashboard. If an attacker gains admin access, this editor gives them the ability to inject arbitrary code. Disable it by adding this to wp-config.php:
// Disable the built-in file editor
define('DISALLOW_FILE_EDIT', true);
// Also consider disabling file modification entirely
// (prevents plugin/theme installation and updates via dashboard)
define('DISALLOW_FILE_MODS', true);
Creating a wp-content Index File
Ensure every directory within wp-content has an index.php file to prevent directory listing:
# Create index.php in all wp-content subdirectories
find wp-content -type d -exec sh -c 'echo "<?php // Silence is golden." > "$1/index.php"' _ {} \;
Special Permissions for Specific Hosting Environments
Permissions that work on a VPS may not work identically on shared hosting, and vice versa. Here are environment-specific considerations.
Shared Hosting (cPanel, DirectAdmin)
On shared hosting, the web server typically runs as a different user than your account. PHP files are executed via CGI/FastCGI or PHP-FPM under your account user. In this case:
- File owner should be your hosting account user (not
www-dataorapache) - Directories:
755 - Files:
644 wp-config.php:600(since the web server runs as your user via suPHP/PHP-FPM)
VPS / Dedicated Server
On a VPS or dedicated server where you control the web server configuration:
- File owner should be the web server user (
www-data,apache, ornginx) - Directories:
755 - Files:
644 wp-config.php:640(owner read/write, group read for the web server)- Consider running PHP-FPM with a separate pool per site for isolation
Docker / Container Environments
In containerized WordPress deployments:
- The container user typically maps to the web server user
- Use the same
755/644pattern inside the container - Mount volumes with appropriate UID/GID mappings
- Consider read-only mounts for
wp-adminandwp-includesin production
Automated Permission Monitoring with VistoShield
Manually checking file permissions across a WordPress installation is tedious and error-prone. A site with 50 plugins might have thousands of files and directories, and a single misconfigured permission can create a vulnerability. This is exactly the kind of task that should be automated.
The VistoShield Security Scanner includes comprehensive file permission checks as part of its automated security scanning. Here is what it monitors:
What VistoShield Scans For
| Check | What It Detects | Severity |
|---|---|---|
| World-writable files (x77 or xx7) | Files that any user on the system can modify | Critical |
| World-writable directories | Directories where any user can create or delete files | Critical |
| Executable uploads | PHP, JS, or shell files in wp-content/uploads/ | Critical |
| Weak wp-config.php permissions | wp-config.php readable by others (644 instead of 640/600) | High |
| Missing .htaccess protections | Uploads directory without PHP execution blocks | High |
| Incorrect ownership | Files owned by root or other non-web-server users | Medium |
| Writable core files | WordPress core files (wp-admin, wp-includes) that are writable | Medium |
Continuous Monitoring vs. One-Time Checks
One-time permission audits are helpful but insufficient. Permissions can change due to plugin installations, updates, backup restorations, FTP operations, or manual SSH commands. VistoShield Security Scanner supports scheduled scans that run daily or weekly, alerting you immediately when permissions drift from the expected baseline. This is far more effective than periodic manual checks.
The scanner integrates with the VistoShield Activity Log to correlate permission changes with specific events. If a permission changes right after a plugin update, you can see the connection and assess whether the change is legitimate or suspicious.
Common Permission Mistakes and How to Fix Them
Here are the most common permission mistakes we see on WordPress sites, along with the fix for each.
Mistake 1: Setting Everything to 777
This is the most dangerous mistake and unfortunately one of the most common. It usually happens when a plugin fails to write to a directory, and the administrator (or a poorly written hosting tutorial) suggests chmod 777 as the fix.
# WRONG - Never do this
chmod -R 777 /var/www/html/wordpress
# CORRECT - Fix with proper permissions
find /var/www/html/wordpress -type d -exec chmod 755 {} \;
find /var/www/html/wordpress -type f -exec chmod 644 {} \;
chmod 640 /var/www/html/wordpress/wp-config.php
Mistake 2: Running WordPress as Root
Running the web server or PHP-FPM as the root user means that every PHP script has full system access. A single vulnerability in any plugin gives the attacker root-level control over the entire server.
# Check if Apache is running as root
ps aux | grep apache | grep -v grep
# Check PHP-FPM pool user
grep "^user" /etc/php/8.2/fpm/pool.d/www.conf
Mistake 3: Ignoring Group Permissions
On servers where the web server user and the file owner are different (common on shared hosting), group permissions matter. If the web server user is in the same group as the file owner, group read permissions (e.g., 640 for wp-config.php) allow PHP to read the file while keeping others out.
Mistake 4: Using FTP Instead of SFTP
FTP clients often upload files with default permissions that may be too permissive. Additionally, FTP transmits credentials in plain text. Always use SFTP (SSH File Transfer Protocol) instead:
# Connect via SFTP
sftp username@your-server.com
# Upload with correct permissions
sftp> put -P local-file.php /var/www/html/wordpress/
Mistake 5: Not Protecting Debug and Log Files
If you have WP_DEBUG_LOG enabled, WordPress writes debug output to wp-content/debug.log. This file can contain sensitive information including database queries, file paths, and error details. Ensure it is not accessible via the web:
# Set restrictive permissions on debug.log
chmod 600 wp-content/debug.log
# Block access via .htaccess
<Files debug.log>
Order Allow,Deny
Deny from all
</Files>
Advanced: Immutable File Attributes
On Linux systems, you can go beyond standard permissions by setting the immutable attribute on critical files. An immutable file cannot be modified, deleted, renamed, or linked to — not even by root — until the immutable attribute is removed.
# Make wp-config.php immutable (requires root)
sudo chattr +i /var/www/html/wordpress/wp-config.php
# Verify the attribute
lsattr /var/www/html/wordpress/wp-config.php
# Output: ----i--------e-- /var/www/html/wordpress/wp-config.php
# To remove immutable attribute (for updates):
sudo chattr -i /var/www/html/wordpress/wp-config.php
Use this technique for files that should never change in production: wp-config.php, .htaccess, and the index.php files in your WordPress root and wp-content directories. Remember to temporarily remove the attribute when you need to make legitimate changes.
File Integrity Monitoring
Even with correct permissions, files can be modified if an attacker gains access through the web server user. File integrity monitoring detects unauthorized changes by comparing files against known-good checksums.
The VistoShield Security Scanner performs file integrity monitoring by:
- Comparing WordPress core files against official checksums from WordPress.org
- Tracking changes to plugin and theme files between scans
- Detecting new files that appear in unexpected locations (especially PHP files in the uploads directory)
- Alerting on modifications to critical files like
wp-config.php,.htaccess, andindex.php
Combined with the Activity Log, file integrity monitoring gives you a complete picture: not just that a file changed, but potentially who changed it and through what mechanism.
Automating Permission Fixes with Scripts
For administrators managing multiple WordPress installations, automating permission fixes saves time and ensures consistency. Here is a comprehensive shell script that sets correct permissions for a WordPress site:
#!/bin/bash
# wordpress-fix-permissions.sh
# Usage: ./wordpress-fix-permissions.sh /path/to/wordpress [web_user]
WP_ROOT="${1:?Usage: $0 /path/to/wordpress [web_user]}"
WEB_USER="${2:-www-data}"
WEB_GROUP="${2:-www-data}"
if [ ! -f "$WP_ROOT/wp-config.php" ] && [ ! -f "$WP_ROOT/wp-settings.php" ]; then
echo "Error: $WP_ROOT does not appear to be a WordPress installation."
exit 1
fi
echo "Setting ownership to $WEB_USER:$WEB_GROUP ..."
chown -R "$WEB_USER:$WEB_GROUP" "$WP_ROOT"
echo "Setting directory permissions to 755 ..."
find "$WP_ROOT" -type d -exec chmod 755 {} \;
echo "Setting file permissions to 644 ..."
find "$WP_ROOT" -type f -exec chmod 644 {} \;
echo "Hardening wp-config.php to 640 ..."
[ -f "$WP_ROOT/wp-config.php" ] && chmod 640 "$WP_ROOT/wp-config.php"
echo "Hardening .htaccess to 644 ..."
[ -f "$WP_ROOT/.htaccess" ] && chmod 644 "$WP_ROOT/.htaccess"
echo "Done. Permissions have been fixed for $WP_ROOT"
Permission Auditing Checklist
Use this checklist for a manual permission audit of your WordPress installation. For automated checks, use the VistoShield Security Scanner.
- No world-writable files or directories — Run
find /var/www/html -perm -o+w -type fandfind /var/www/html -perm -o+w -type d. Both should return empty results. - wp-config.php is restricted — Permissions should be
640or600, never644or higher. - No PHP files in uploads — Run
find wp-content/uploads -name "*.php". Should return nothing. - .htaccess protects uploads — Verify that
wp-content/uploads/.htaccessexists and blocks PHP execution. - Directory browsing disabled — Check
Options -Indexesis present in.htaccess. - File editor disabled — Verify
DISALLOW_FILE_EDITis defined inwp-config.php. - Correct ownership — Files should be owned by the web server user, not root.
- Debug log protected — If
WP_DEBUG_LOGis enabled,debug.logshould not be web-accessible.
Getting Started with VistoShield
Correctly configured file permissions are a critical layer in WordPress security, but they are just one piece of the puzzle. The VistoShield security suite provides five interconnected plugins that protect your WordPress site from multiple angles:
- Security Scanner — Automated file permission checks, file integrity monitoring, malware scanning, and vulnerability detection.
- Firewall & WAF — Blocks malicious requests before they reach your application, including attempts to exploit file upload vulnerabilities.
- Login Guard — Prevents brute-force attacks, provides two-factor authentication, and controls who can access your admin area.
- Bot Detector — Identifies and blocks malicious bots that scan for misconfigurations and vulnerable files.
- Activity Log — Tracks every change on your site, including file modifications, permission changes, and user actions.
All five plugins are available in the free tier under the GPLv2 open-source license. For advanced features, the Single Pro plan starts at €19/site/year, the Pro Bundle is €49/site/year, and the Agency Bundle covers 25 sites for €149/year. Visit the VistoShield homepage to get started, or explore the Security Scanner documentation for detailed setup instructions.