← Back to Blog

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.

WordPress File Permissions: The Complete Security Guide

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

PathTypeRecommendedDangerousWhy
/ (WordPress root)Directory755777Web server needs to traverse, not write
wp-admin/Directory755777Admin files should not be writable by others
wp-includes/Directory755777Core files must remain unmodified
wp-content/Directory755777Parent directory for plugins, themes, uploads
wp-content/uploads/Directory755777Must be writable by web server, not world-writable
wp-content/plugins/Directory755777Plugin code must not be modifiable by others
wp-content/themes/Directory755777Theme code must not be modifiable by others
.htaccessFile644666/777Controls URL rewriting and access rules
wp-config.phpFile640 or 600644/666Contains database credentials and auth keys
All other .php filesFile644666/777PHP 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:

  1. An attacker exploits a vulnerability in a plugin (e.g., a file upload form that doesn’t properly validate file types)
  2. They upload a PHP file disguised as an image (e.g., shell.php.jpg or payload.php) to wp-content/uploads/
  3. They access the file directly via the browser: https://yoursite.com/wp-content/uploads/2026/03/payload.php
  4. 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 755 for directories and 644 for files within uploads.
  • Disable directory listing within uploads to prevent attackers from browsing uploaded files.
  • Add an empty index.php to 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-data or apache)
  • 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, or nginx)
  • 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/644 pattern inside the container
  • Mount volumes with appropriate UID/GID mappings
  • Consider read-only mounts for wp-admin and wp-includes in 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

CheckWhat It DetectsSeverity
World-writable files (x77 or xx7)Files that any user on the system can modifyCritical
World-writable directoriesDirectories where any user can create or delete filesCritical
Executable uploadsPHP, JS, or shell files in wp-content/uploads/Critical
Weak wp-config.php permissionswp-config.php readable by others (644 instead of 640/600)High
Missing .htaccess protectionsUploads directory without PHP execution blocksHigh
Incorrect ownershipFiles owned by root or other non-web-server usersMedium
Writable core filesWordPress core files (wp-admin, wp-includes) that are writableMedium

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, and index.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 f and find /var/www/html -perm -o+w -type d. Both should return empty results.
  • wp-config.php is restricted — Permissions should be 640 or 600, never 644 or higher.
  • No PHP files in uploads — Run find wp-content/uploads -name "*.php". Should return nothing.
  • .htaccess protects uploads — Verify that wp-content/uploads/.htaccess exists and blocks PHP execution.
  • Directory browsing disabled — Check Options -Indexes is present in .htaccess.
  • File editor disabled — Verify DISALLOW_FILE_EDIT is defined in wp-config.php.
  • Correct ownership — Files should be owned by the web server user, not root.
  • Debug log protected — If WP_DEBUG_LOG is enabled, debug.log should 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.

Ready to try VistoShield?

Free and open source. Get started in 60 seconds.

Get Started Free