Quick answer

To protect WordPress files, set files to 644 and folders to 755, lock wp-config.php to 400 or 440, turn off the dashboard file editor, stop PHP from running in wp-content/uploads, and block direct access to include-only files, backups, and logs. Then check core and plugin files against WordPress.org checksums every month.

I tested every rule in this guide on a clean Apache 2.4 server and on Nginx before writing it down, using a fake WordPress folder full of files that should and should not load.

Each section shows what the rule blocks and what it leaves alone. That second part matters: a file rule that also blocks your images or the admin area is worse than having no rule at all.

The checklist
Protection Where Time 1. File permissions SSH or SFTP 10 min 2. Lock wp-config.php wp-config.php and .htaccess 5 min 3. Turn off the file editor wp-config.php 1 min 4. No PHP in uploads uploads/.htaccess 2 min 5. Block include-only files, leftovers, and wp-cron.php .htaccess, server cron 10 min 6. Verify checksums and cron events WP-CLI 5 min a month 7. Off-site backups and SFTP Backup tool, FTP client Ongoing
Step 1

How to set the right WordPress file permissions

The first step to protect WordPress files is permissions: 644 for files, 755 for folders, and 400 or 440 for wp-config.php. That is the scheme in WordPress's own file permissions documentation, and it means only your account can change files while the web server can still read them.

What Permission What it allows Files 644 You read and write; everyone else reads Folders 755 You change contents; everyone else lists and enters wp-config.php 400 or 440 Read only, and only by you (and your group with 440) Anything 777 Anyone on the server can change it. Never use it

With SSH access, two commands reset a whole install. Replace the path with your WordPress folder:

Terminal (SSH) BASH
find /path/to/wordpress/ -type d -exec chmod 755 {} \;
find /path/to/wordpress/ -type f -exec chmod 644 {} \;
chmod 400 /path/to/wordpress/wp-config.php

No SSH? In an SFTP client such as FileZilla, right-click a file or folder, choose File permissions, and enter the number. For folders, tick the option to apply it to subdirectories.

Ownership matters as much as the numbers. WordPress recommends that files belong to your hosting account user, not to the web server user, with wp-content writable by both so uploads and updates still work.

About 777

Any guide or plugin that tells you to chmod a folder to 777 is asking you to let every process on the server write there. If an upload fails, the real fix is usually ownership, not wider permissions.

Step 2

How to protect wp-config.php

wp-config.php holds your database password and security keys, so it gets three layers: strict permissions, a server rule that refuses web requests for it, and optionally a new location outside the public folder.

Set wp-config.php to 400 or 440

Terminal (SSH) BASH
chmod 400 wp-config.php

If the site shows "Error establishing a database connection" straight afterwards, the web server can no longer read the file. Switch to 440, or 600 on hosts where PHP runs as your own user. Our guide to the database connection error covers the other causes if it persists.

Block web requests for wp-config.php

Add this to the .htaccess file in your WordPress root, outside the # BEGIN WordPress and # END WordPress lines so WordPress never overwrites it:

.htaccess (WordPress root) APACHE
<Files "wp-config.php">
    Require all denied
</Files>

On Nginx, which ignores .htaccess, the same protection goes in the server block. The full Nginx set is in step 5.

Optionally move it one folder up

WordPress looks for wp-config.php one directory above the install if it is not in the root, so you can move it out of the public folder entirely. This only helps when that parent folder is outside the web root, and it does not work when another WordPress install already lives there.

Step 3

How to turn off the file editor in the dashboard

Add one line to wp-config.php, above the line that says "That's all, stop editing!":

wp-config.php PHP
define( 'DISALLOW_FILE_EDIT', true );

This removes the theme and plugin file editors from the dashboard. If someone steals an admin password, they can no longer paste PHP into your theme from a browser, which is one of the fastest ways a stolen login becomes a hacked site.

A stronger option exists: DISALLOW_FILE_MODS also blocks installing, updating, and deleting plugins and themes from the dashboard, including automatic updates. Use it only if updates run through WP-CLI or a deployment process, or your plugins will quietly fall behind.

Step 4

How to stop PHP running in wp-content/uploads

The uploads folder should only ever hold images, PDFs, and other media, never code. Blocking PHP there means that if an attacker does get a file uploaded through a vulnerable plugin, requesting it returns a 403 instead of running it.

Create a file named .htaccess inside wp-content/uploads with this rule:

wp-content/uploads/.htaccess APACHE
# Block PHP from running in the uploads folder.
<FilesMatch "\.(?i:php[0-9]?|phtml|phar)$">
    Require all denied
</FilesMatch>

The (?i:...) part makes the match case-insensitive. In my Apache test, it blocked shell.php, shell.PHP, shell.phtml, and shell.php5, while photo.jpg and doc.pdf in the same folder still loaded normally.

If a plugin stops working after this, it was running PHP from your uploads folder. That is a design choice worth asking its developer about rather than a reason to remove the rule.

Step 5

How to block include-only files, backups, logs, and wp-cron.php

A default install also exposes files nobody needs to request directly: PHP files meant only to be included, version details in readme.html, and anything left behind by backups or debugging. These rules go in the root .htaccess, again outside the WordPress block.

Include-only files in wp-includes and wp-admin/includes

This block comes straight from WordPress's hardening guide. Do not use it on Multisite, where it interferes with how uploaded files are served.

.htaccess (WordPress root) APACHE
# Block the include-only files.
<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>

Directory listings

Without an index file, some servers list every file in a folder, which hands out your whole uploads archive. One line turns that off:

.htaccess (WordPress root) APACHE
Options -Indexes

Backups, logs, and install leftovers

.htaccess (WordPress root) APACHE
# Block backups, logs, and install leftovers.
<FilesMatch "\.(bak|old|orig|sql|log|swp)$|^(readme\.html|license\.txt|wp-config-sample\.php)$">
    Require all denied
</FilesMatch>

This covers files like wp-config.php.bak, database dumps, and wp-content/debug.log, which can reveal server paths. Blocking readme.html and license.txt works better than deleting them, because every WordPress update puts them back and the checksum check in step 6 flags them as missing.

Move wp-cron to a server cron, then block wp-cron.php

WordPress runs scheduled tasks through wp-cron.php, which any visitor can request. Repeated requests to it make the server run cron work on demand, so on busy or targeted sites it becomes an easy way to add load. The fix has three parts, and the order matters.

Do this in order

If you block wp-cron.php before a server cron is running, scheduled posts stop publishing and show "Missed schedule", and plugin updates, backups, and emails that depend on cron stop too. Some managed hosts already call wp-cron.php over the web for you; ask yours before blocking it.

First, stop WordPress from running cron on page loads by adding this to wp-config.php:

wp-config.php PHP
define( 'DISABLE_WP_CRON', true );

Second, add a real cron job in your hosting panel or crontab that runs due events every five minutes through WP-CLI. It runs on the server itself, so it keeps working after the web block:

crontab BASH
*/5 * * * * cd /path/to/wordpress && wp cron event run --due-now >/dev/null 2>&1

Third, once a scheduled test post has published on time, block web requests for the file:

.htaccess (WordPress root) APACHE
# Block web requests for wp-cron.php (only after a server cron is set up).
<Files "wp-cron.php">
    Require all denied
</Files>
nginx server block NGINX
# Block web requests for wp-cron.php (only after a server cron is set up).
location = /wp-cron.php { deny all; }

On both servers in my test, wp-cron.php returned 403, with or without the ?doing_wp_cron query string, while pages, images, and wp-admin kept loading.

How to protect WordPress files on Nginx

Nginx does not read .htaccess, so everything goes in the server block. Place these above your main location ~ \.php$ block, because Nginx uses the first regex location that matches. Add the wp-cron.php rule above only if the server cron is in place:

nginx server block NGINX
# Block direct access to wp-config.php.
location = /wp-config.php { deny all; }

# Block PHP from running in the uploads folder.
location ~* ^/wp-content/uploads/.*\.(php[0-9]?|phtml|phar)$ { deny all; }

# Block the include-only files.
location ~ ^/wp-admin/includes/ { deny all; }
location ~ ^/wp-includes/[^/]+\.php$ { deny all; }
location ~ ^/wp-includes/js/tinymce/langs/.+\.php$ { deny all; }
location ~ ^/wp-includes/theme-compat/ { deny all; }

# Block backups, logs, and install leftovers.
location ~* \.(bak|old|orig|sql|log|swp)$ { deny all; }
location ~* ^/(readme\.html|license\.txt|wp-config-sample\.php)$ { deny all; }

# Block hidden files such as .htaccess and .git, but keep .well-known.
location ~ /\.(?!well-known) { deny all; }

In my Nginx test, these returned 403 for every protected path while images, wp-admin, normal pages, and /.well-known/ (which SSL certificate renewals need) kept working. The last rule also hides .git folders and other dotfiles, which Apache does not block by default.

Step 6

How to check WordPress files for unexpected changes

WP-CLI gives you two monthly checks: whether your core and plugin files still match the official copies, and whether anything unfamiliar is scheduled to run.

Compare core and plugin files against checksums

The core check compares WordPress itself against the official copies on WordPress.org and lists anything that differs; the plugin check does the same for every plugin hosted on WordPress.org.

$ wp core verify-checksums Success: WordPress installation verifies against checksums.
$ wp plugin verify-checksums --all Success: Verified 8 of 8 plugins.

Neither command checks your theme, your uploads, or premium plugins bought outside WordPress.org, so they are a first pass rather than a full scan. If core files fail, replace them with clean copies; our guide to replacing WordPress core files walks through it safely.

Run both once a month. A modified core file you did not change is one of the clearer signs a WordPress site is hacked.

Check scheduled cron events

Malware often leaves a scheduled event behind that rewrites files after you clean them, which is why a site can look fixed and be reinfected a day later. List every scheduled event:

$ wp cron event list --fields=hook,next_run_relative,recurrence wp_version_check 11 hours 57 minutes 12 hours

Core hooks mostly start with wp_, and plugins usually name theirs after the plugin. A hook with a random-looking name, or one you cannot trace to anything installed, is worth investigating before you delete it.

Step 7

Keep off-site backups and use SFTP

File protections reduce the chance of a break-in; backups decide how bad one gets. Keep at least one copy of your files and database off the server, and test a restore now and then. Our WordPress backup guide covers what a useful backup includes.

Connect with SFTP rather than plain FTP. FTP sends your password unencrypted, which undoes careful permissions the moment someone captures it.

Check your work

How to test your WordPress file protections

Request each protected path and confirm the status code. A 403 means the rule works; a 200 on a sensitive file means it does not. Upload a harmless test.php to your uploads folder first, and delete it afterwards.

Request Expected /wp-config.php 403 /wp-content/uploads/test.php 403 /wp-includes/version.php 403 /readme.html 403 /wp-content/uploads/ 403 Any image in /wp-content/uploads/ 200 Your homepage and /wp-admin/ 200
$ curl -s -o /dev/null -w "%{http_code}\n" https://yoursite.com/wp-config.php 403
Troubleshooting

What if a file protection breaks your site?

Every change here can be undone in under a minute over SFTP, which is why you should keep the connection open while you work.

01

500 error right after editing .htaccess

Usually a typo or a directive your server does not allow. Rename .htaccess to .htaccess-off to get the site back, then add the rules one block at a time. Our 500 internal server error guide covers the other causes.

02

403 on the whole site

Permissions are too strict or ownership is wrong, often a folder set below 755. Reset with the find commands in step 1. Here is how to work through a WordPress 403 forbidden error if that does not clear it.

03

Database connection error after changing wp-config.php

The web server cannot read the file at 400. Change it to 440, or 600 where PHP runs as your user.

Key takeaways

Protecting WordPress files comes down to three questions for each file: who can read it, who can change it, and can it run as code. Permissions of 644 and 755, with 400 or 440 on wp-config.php, answer the first two.

Server rules answer the third. Block PHP in wp-content/uploads, deny web requests for wp-config.php and include-only files, and hide backups, logs, and readme.html. Block wp-cron.php too, but only once a server cron runs your scheduled tasks. Every rule in this guide returned 403 on its target in testing and left images, pages, and wp-admin working.

Then check the files themselves every month with WP-CLI checksums, keep an off-site backup, and read our broader guide on how to secure a WordPress site for the login, update, and plugin side of security.

Frequently asked questions

What permissions should WordPress files have?

WordPress recommends 644 for files and 755 for folders, with wp-config.php set to 400 or 440. Files should be owned by your hosting account user rather than the web server user, and wp-content needs to stay writable by the web server so uploads and updates work. Never set anything to 777.

Should wp-config.php be 400 or 440?

Start with 400, which lets only the file owner read it. If the site then shows a database connection error, the web server cannot read the file, so switch to 440. On hosts where PHP runs as your own account user, 600 is the setting that works reliably.

How do I stop PHP files running in the WordPress uploads folder?

Create an .htaccess file inside wp-content/uploads with a FilesMatch rule that denies .php, .phtml, .phar, and numbered PHP extensions, case-insensitively. On Nginx, add a location block that denies the same extensions under /wp-content/uploads/. Images, PDFs, and other media keep loading normally.

Should I disable wp-cron.php in WordPress?

Disable it only after setting up a real server cron. Add DISABLE_WP_CRON to wp-config.php, schedule WP-CLI to run due events every five minutes, confirm a scheduled post publishes on time, and then block web requests for wp-cron.php. Blocking it first stops scheduled posts, updates, and backups.

What does DISALLOW_FILE_EDIT do in WordPress?

Setting DISALLOW_FILE_EDIT to true in wp-config.php removes the theme and plugin file editors from the dashboard. Anyone who steals an admin login can no longer paste PHP into your theme from a browser. It does not block plugin installs or updates; DISALLOW_FILE_MODS does that.

Do .htaccess rules work on Nginx?

No. Nginx ignores .htaccess files completely, so the same protections must go into the server block as location rules. Put them above the main PHP location, because Nginx uses the first matching regex location. If you cannot edit the server config, ask your host to add them.

Want your WordPress files locked down properly?

Survyc is an AI-first digital agency. We set permissions and ownership, add server rules for Apache or Nginx, and test every protected path so nothing legitimate breaks. Our WordPress security work covers this and the rest of the hardening list.

If you are protecting WordPress files because something already looks wrong, our malware removal service cleans the site first. Send us the URL, or email info@survyc.com.

Talk to us

Last reviewed: September 2026. Rules tested on Apache 2.4.68 and Nginx 1.31. Hosts differ in what they allow in .htaccess, so this guide is reviewed every quarter.