What a 403 actually means
The server understood your request, identified what you wanted, and refused. Something made a decision. Your job is to find which layer made it, and there are only six realistic candidates.
That framing matters because a 403 is the error people most often try to fix by guessing. Resetting permissions when a security plugin blocked your IP changes nothing, and resetting them to 777 makes the site insecure and frequently still returns 403, because many hosts refuse to execute world-writable files.
Narrow it in two minutes before changing anything
Four questions. The answers eliminate most of the six causes immediately.
| Question | If yes, look at |
|---|---|
| Is it the whole site, every URL? | File rules, permissions, or an index problem |
Only /wp-admin or wp-login.php? | Security plugin lockout, or an admin-specific rule |
| Only one URL or one action? | mod_security at the host, or a rule matching that request |
| Only for you, while others are fine? | Your IP is blocked. Almost always this |
Test the last one properly: load the site on mobile data with wifi off, which gives you a different IP. If it works there and not on your network, you have your answer and the fix is an allowlist entry, not a file change.
# see the actual status and which server answered
curl -sI https://example.com/ | head -20
curl -sI https://example.com/wp-admin/ | head -20
# compare a normal browser request with a bare one, since some rules
# refuse requests with no user agent
curl -sI -A "Mozilla/5.0" https://example.com/ | head -5
Response headers often name the culprit. A server or x-powered-by header from a security product, or a body mentioning a firewall, tells you where to look without any guessing.
The six causes, in order of how often they are the answer
- A rule in
.htaccessor the server config. Either added deliberately, written by a plugin, or corrupted. - Wrong permissions or ownership. Common immediately after a migration or a restore from backup.
- A security plugin lockout. Failed logins, a country block, or a rule you forgot was on.
- Host-level mod_security. Returns 403 on requests matching its signatures, including some legitimate admin posts.
- Hotlink protection or a missing directory index. Affects specific assets or directory URLs.
- A compromise. Injected rules, or a file that should not be there. Rare, but check if nothing else fits.
File rules: test by renaming, never by editing blind
On Apache and LiteSpeed, .htaccess is the first place to look. The safe test is to take it out of play rather than guess at a line.
# keep a copy, then neutralise it
cp .htaccess .htaccess.broken
mv .htaccess .htaccess.off
# test the site, then restore either way
mv .htaccess.off .htaccess
If the 403 disappears with the file renamed, the cause is in it. Restore it and let WordPress write a clean one: go to Settings, then Permalinks and press Save without changing anything, which rewrites the WordPress block correctly.
Then read what you kept in the copy. You are looking for a Require or deny directive, an Options -Indexes on a directory being requested, a RewriteRule returning [F], or a <Files> block covering more than intended. Also check for a second .htaccess inside wp-admin or wp-content, because a nested one overrides the root for that directory and is easily forgotten.
# find every .htaccess under the site, not just the root
find . -name ".htaccess" -type f -exec echo "--- {}" ; -exec cat {} ;
On nginx there is no .htaccess, so the equivalent lives in the server block and you will usually need the host to read it. Say that you are getting a 403 and ask them to check the error log for your IP and timestamp.
Permissions and ownership
The standard is directories 755, files 644, and wp-config.php tighter at 640 or 600.
# see what you actually have
ls -la | head -20
stat -c "%a %U:%G %n" wp-config.php index.php wp-admin wp-content
# reset to the normal defaults
find . -type d -exec chmod 755 {} ;
find . -type f -exec chmod 644 {} ;
chmod 640 wp-config.php
Run those two find commands from the WordPress root and nowhere else. Run them a directory too high and you will change permissions across unrelated files.
Ownership matters as much as the mode and is missed more often. After a migration or a restore, files frequently end up owned by root while the web server runs as something else. The mode looks right and the server still refuses.
# who owns the files, and who does the web server run as
ls -ld . wp-content
ps -eo user,comm | grep -E "apache|httpd|nginx|php-fpm" | sort -u
If those disagree, ask your host to correct ownership. Do not guess at a chown on shared hosting.
Never use 777. It makes every file writable by anything on the server, and many hosts refuse to execute world-writable PHP, so it causes 403s as well as creating a serious security problem.
Security plugins, firewalls and the host
If the 403 is admin-only, or only affects you, start here. A lockout after failed logins is the single most common cause of an admin-only 403, and the person locked out is usually the one who typed the password wrong three times.
You can reach the lockout list without logging in, if you have file or CLI access, by disabling the plugin temporarily.
# which security plugins are active
wp plugin list --status=active --fields=name,version | grep -iE "wordfence|security|firewall|ithemes|sucuri|aios|limit-login"
# turn one off just long enough to confirm it is the cause
wp plugin deactivate the-plugin-name
# ...test the admin, then
wp plugin activate the-plugin-name
With no CLI, rename the plugin's folder inside wp-content/plugins to disable it, then rename it back. Use this to confirm the cause, then fix it properly in the plugin's settings by allowlisting your IP rather than leaving protection off.
If every one of your own checks comes back clean, it is probably the host. Ask them directly: "I am getting a 403 at this URL, at this time, from this IP. Can you check the mod_security log?" That phrasing gets a useful answer, because host firewalls commonly block legitimate admin requests such as saving a long post or a page builder layout, and the entry is sitting in a log you cannot see.
What a 403 is not
Ruling out saves more time than guessing. None of these produce a true 403.
| Symptom | Not a 403, but |
|---|---|
| White screen with no error | A PHP fatal error. Read the log, not the permissions |
| Page not found | 404: permalinks or a missing page |
| Site briefly unavailable for maintenance | A stuck update. See our guide below |
| Too many redirects | A redirect loop, usually a URL or SSL mismatch |
| Error establishing a database connection | Credentials or the database server, nothing to do with permissions |
| 403 only in one browser | A cached response or an extension. Try a private window first |
That last row catches people regularly. Browsers cache error responses, so a 403 you already fixed can persist on your machine alone. Check in a private window before concluding the fix failed.
For the adjacent errors, see stuck in maintenance mode and too many redirects.
The order to work through it
- Establish the scope: whole site, admin only, one URL, or only you
- Try mobile data. If it works, your IP is blocked and you are done
- Check response headers with
curl -sIfor a product name - Rename
.htaccess, test, restore, and resave permalinks - Check permissions and ownership against the defaults above
- Disable security plugins one at a time, confirming each
- Ask the host to read the mod_security log with your IP and timestamp
- If nothing fits, treat it as a possible compromise and scan
Steps one and two solve a large share of cases in under five minutes and cost nothing. Do them before touching a single file.
If you are stuck at step seven with a site that is down, that is a reasonable point to hand it over. We fix WordPress sites that are returning errors and tell you what caused it rather than just clearing it. Send us the URL and what you see.