You open your WordPress site and the browser shows "403 Forbidden" instead of the page. Sometimes only one URL is blocked. Sometimes every page on the site. Sometimes a specific image or CSS file is the one returning 403 while the HTML around it loads fine. The request reached your server, your server decided not to serve it, and it did so on purpose.
Scope of this article
This article is about 403 Forbidden on the public side of a WordPress site: visitors, a specific URL, an image, a whole section, or the front end in general. If the 403 only appears when you try to reach wp-admin or wp-login.php and the rest of the site works, that is a different diagnosis path and lives in 403 Forbidden on wp-admin. The five causes below overlap, but the admin-only flavor has its own order of likelihood and deserves its own walkthrough.
What a 403 actually means
RFC 9110 §15.5.4 defines 403 as: "the server understood the request but refuses to authorize it." That sentence matters because it rules out two things. The URL is not broken, so this is not a 404. The application did not crash, so this is not a 500. Something in the chain from browser to PHP deliberately decided "not you, not this, not now".
On a WordPress site, "something in the chain" is almost always one of five things: a web application firewall blocking the request, file permissions too restrictive for the web server user to read the file, an .htaccess directive that denies access, Apache or nginx refusing to list a directory without an index file, or a hotlink rule intercepting an asset. The important thing about that list is the order. In 2026, the WAF case is by far the most common cause I see on live WordPress sites, and the order below reflects that.
Common causes, ordered by likelihood
1. A WAF or ModSecurity rule is blocking the request
This is the top cause on any WordPress site that runs behind a security layer, which in 2026 is most of them. ModSecurity returns 403 Forbidden by default when a rule fires, and so do Cloudflare, Sucuri, Wordfence Central, and most managed hosting WAFs. The block is not always about you: the OWASP Core Rule Set scores each request against many pattern checks, and a legitimate request that happens to contain certain words in a URL parameter, a long query string, or a familiar attack shape can cross the threshold. Typical symptoms: the 403 appears on a specific URL or form submission, the error page looks generic and is not from WordPress, the request works from a different network or device, and the front end is otherwise healthy.
2. File or directory permissions (see WordPress file permissions for the full reference) are wrong
If the web server user cannot read a file, it cannot serve it, and Apache or nginx returns 403. The WordPress file permissions documentation recommends 755 for directories, 644 for files, and 440 or 400 for wp-config.php. A migration that preserved the wrong owner, a hardening script that set everything to 600, or a manual chmod 700 on wp-content/uploads/ will produce 403 on whatever the server can no longer read. Symptom: the 403 appears on a specific file or a whole directory rather than a WordPress URL pattern, and the server's error log shows Permission denied.
3. An .htaccess directive is denying access (Apache only)
On Apache or LiteSpeed, .htaccess can explicitly forbid access to a path. Apache's Require all denied returns 403 to every client that matches the scope of the block, and so do older Deny from all directives, RewriteRule ... [F] rules, and custom <Files> or <FilesMatch> blocks that a plugin or security tool installed. The most common version I see: a maintenance plugin that added a deny block and did not clean it up, or a migration that copied .htaccess from a different domain where certain paths were deliberately locked. Symptom: the 403 is surgical (one path, one file pattern) and disappears instantly when you rename .htaccess. nginx does not read .htaccess, so skip this cause on nginx.
4. The directory has no index file and indexing is off
This is the most predictable 403 on the list. If a visitor hits a URL like https://yoursite.nl/wp-content/uploads/2026/03/ directly, and there is no index.html or index.php in that folder, Apache with Options -Indexes returns 403 Forbidden rather than list the directory contents. On nginx the same thing happens when autoindex is off, which is the default. This is correct behavior for privacy reasons. It only looks like an error because the visitor expected something useful. Symptom: a 403 that only appears on URLs ending in a slash where no index file exists, and that a human being would not normally visit.
5. A hotlink protection rule intercepts an asset
If your site loads an image, font, or PDF and the server returns 403 for that specific asset, the cause is often a hotlink protection rule that checks the Referer header and rejects requests that do not come from your own domain. The rule lives in .htaccess, in a CDN rule, in a security plugin, or in the hosting control panel. It is supposed to block other sites from embedding your images. It accidentally blocks your own site when the rule list is too strict, when you added a new domain or subdomain, or when a browser sends an empty Referer because the user clicked from an HTTPS origin with a strict referrer policy. Symptom: the page HTML loads fine, but specific assets return 403 in the browser devtools network tab.
Diagnose which cause applies
Run these checks in order. They are non-destructive and they isolate which of the five causes is yours before you touch anything.
Check 1: what is the exact scope of the 403? Is it one URL, one file type, one directory, the whole front end, or only specific assets on an otherwise working page? Note the URL that returns 403 and the URL of one that works. The scope alone rules out most causes. A single file 403 almost never comes from a WAF. A whole-site 403 almost never comes from a missing directory index. You will know it worked when: you can name the exact set of URLs that 403 and the exact set that load.
Check 2: does the 403 page look generic or custom? Open the failing URL and look at the actual HTML. A Cloudflare block page, a Sucuri block page, a Wordfence block page, or a plain "ModSecurity Action" page points directly at cause 1. A bare Apache or nginx 403 page (no branding) usually points at cause 2, 3, or 4. A page that does not load an image while everything else is fine points at cause 5. You will know it worked when: you can describe the source of the 403 page.
Check 3: read the server error log. Open the error log in your hosting panel or over SSH. A Permission denied line naming a file is cause 2. An Options ExecCGI is off or client denied by server configuration line is cause 3. A directory index forbidden line is cause 4. A ModSecurity: Access denied line naming a rule ID is cause 1. On nginx the messages live in /var/log/nginx/error.log per the nginx documentation. On Apache they live in /var/log/apache2/error.log or /var/log/httpd/error_log. You will know it worked when: you can quote the exact line matching the timestamp of the 403.
Check 4: try the URL from a second network or device. If the 403 goes away when you switch to mobile data or a different browser, you are almost certainly looking at cause 1 with a WAF that blocklisted your IP, a geo rule, or a rate limit. If the 403 is identical from every network, the block is not about who you are, and causes 2 through 5 become the likely suspects. You will know it worked when: you can say "the URL loads from my phone" or "the URL 403s from every device I tried".
Check 5: on Apache, rename .htaccess and retest. If the 403 disappears the moment .htaccess is renamed, cause 3 is confirmed. If it does not, rename the file back and rule the file out. nginx ignores .htaccess, so skip this step on nginx. You will know it worked when: the failing URL either loads with .htaccess out of the way, or still 403s and you can cross cause 3 off the list.
Solutions, per cause
Cause #1 fix: identify and tune the WAF rule
If the 403 page is branded, open it and note the rule name or ID it shows. Cloudflare, Sucuri, and Wordfence all print the rule in the block page. ModSecurity prints the rule ID in the audit log (usually /var/log/apache2/modsec_audit.log or your host's control panel), and the default block code is 403 unless the rule sets a different status with something like deny,status:403 per the reference manual above. With a specific rule ID in hand, you have three options, in order of increasing effort:
- Whitelist your own IP for the failing URL in the WAF panel, if the block is about who you are. This is the right fix for a blocklist entry or a rate limit, not for a false positive on the request itself.
- Ask your host to add a rule exception for the specific rule ID and the specific path, not the whole site. A single rule exception is safer than disabling the whole WAF.
- If the WAF is a WordPress plugin you installed (Wordfence, All-in-One Security, iThemes Security), open its firewall log from
wp-admin, find the blocked request, and click the "allowlist" button next to it. Every mainstream plugin has this.
Do not disable the WAF globally to "see if that fixes it". A temporary bypass is fine for ten minutes to confirm the cause; a permanently disabled WAF is a bigger problem than the 403.
Verification: the failing URL loads, the WAF log shows an allow entry instead of a block, and the fix targets a specific rule or IP rather than turning off protection wholesale.
Cause #2 fix: restore file permissions
Most hosting panels include a file manager that can show and change permissions. Open the file manager, navigate to the file or directory that the error log named, and check its permission value. The WordPress file permissions documentation recommends 755 for directories, 644 for files, and 440 or 400 for wp-config.php. If the file manager shows something more restrictive (like 600 or 700), change it to the recommended value. Many file managers let you apply permissions recursively to a whole directory tree, which is useful if an entire folder like wp-content/uploads/ is affected.
On some shared hosts the working values are 750 for directories and 640 for files instead, because PHP runs as a different user than the owner. If 644 still produces 403, try 640 next. Do not set anything to 777. The WordPress docs are explicit that world-writable permissions are dangerous, and 403 is not a permissions problem you solve with more permissions. It is a permissions problem you solve with the right permissions.
If the 403 is isolated to one directory like wp-content/uploads/2026/03/, start there before changing permissions across the whole tree.
If you have SSH access:
find . -type d -exec chmod 755 {} \; # directories
find . -type f -exec chmod 644 {} \; # files
chmod 440 wp-config.php # wp-config.php hardened per WordPress docs
Verification: the failing URL or asset loads again, the file's permissions in your hosting panel's file manager (or via ls -l over SSH) show at least read access for the web server user, and the server error log no longer contains Permission denied lines for that path.
Cause #3 fix: remove or correct the denying directive (Apache only)
If Check 5 confirmed that removing .htaccess fixed the 403, open the file and look for the block that denies access. Typical patterns to search for:
Require all deniedinside a<Files>,<FilesMatch>,<Directory>, or<Location>blockDeny from all(the Apache 2.2 syntax, still common in copy-pasted snippets)RewriteRulelines ending in[F], which returns 403- A custom plugin block named after a security or maintenance tool
Remove the block that covers your failing path, save, and test. If you do not recognize the block, make a copy of the file first so you can restore it. The safest way to restore a clean WordPress .htaccess is to let WordPress regenerate it: from wp-admin, go to Settings → Permalinks and click Save Changes without changing anything. WordPress writes a fresh block matching the default WordPress rewrite block. That only works if .htaccess is writable by the web server user; if it is not, create the file yourself with the default block and upload it.
Verification: the failing URL loads, .htaccess no longer contains the denying directive, and WordPress permalinks still work (the pretty URLs resolve).
Cause #4 fix: add an index file or accept the 403
If a visitor is reaching a directory URL directly (wp-content/uploads/2026/03/), the cleanest fix is to add an empty index.html or index.php to that directory. WordPress itself ships empty index.php files in many directories for exactly this reason: they return a blank page instead of a 403 and they prevent directory listing at the same time. For a directory that should not be reachable at all, the 403 is the correct answer and you should leave it. The question is whether a visitor ever needs to land on that URL. If yes, add an index file. If no, leave the 403 and fix whatever links point at the directory URL instead of the specific file.
Do not enable Options +Indexes on Apache or autoindex on; on nginx as a fix. That exposes the directory contents to anyone and is almost never what you want on a WordPress site.
Verification: the directory URL now returns your index file (or continues to return 403 deliberately), and no legitimate link on the site points at a directory URL that depends on Indexes.
Cause #5 fix: adjust the hotlink protection rule
Find the rule first. It lives in .htaccess (look for a RewriteCond %{HTTP_REFERER} block), in a CDN dashboard (Cloudflare calls it "Hotlink Protection"), in a security plugin, or in the hosting control panel's asset protection section. Once you find it, add your own domain (including www, non-www, and any subdomains you use) to the allowed referrers list, and allow empty referrers if your site uses a strict referrer policy or if you link to assets from HTTPS pages that hide the full referrer path. A typical .htaccess hotlink block that actually works for your own site looks like this:
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$ [NC]
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?yoursite\.nl [NC]
RewriteRule \.(jpg|jpeg|png|gif|webp|svg|pdf)$ - [F]
The first RewriteCond line is the important one. It allows empty referrers, which is what a lot of modern browsers send on HTTPS pages. Without that line, your own CSS and HTML references to images can trigger the hotlink rule on the wrong visit.
Verification: the asset loads on your own pages in the browser devtools network tab (200 response), and an embed attempt from an external domain still returns 403.
When to escalate
If 20 minutes of diagnosis have not pinpointed the cause, hand the incident to your host or a developer with this list ready. It routes the ticket to the right engineer on the first round:
- The exact URL that returns 403, and one URL on the same site that loads fine.
- The scope: one URL, one directory, a specific asset type, or the whole front end.
- The 403 page itself: generic Apache or nginx, Cloudflare, Sucuri, Wordfence, a security plugin, or a ModSecurity block page.
- Your stack: Apache or nginx, PHP version, WordPress version, whether it is behind a CDN or WAF.
- The matching lines from the server error log (Apache, nginx) or the WAF audit log, with timestamps.
- Whether the same URL loads from a different network or device.
- What changed in the last 48 hours: plugin updates, theme updates, core updates, WAF rule updates, permission changes, migrations, DNS changes.
- On Apache: whether renaming
.htaccesschanges the behavior.
If the 403 is only on wp-admin, do not start with this article. Follow the wp-admin variant of this diagnosis instead, since the likelihood order and fixes differ. If you can reach the URL but not log in, the underlying problem is probably in the cannot log in to WordPress family.
How to prevent it from coming back
Three habits keep 403s rare on a healthy WordPress site:
- Run WAF rule exceptions as patches, not blanket bypasses. Every time a WAF blocks something legitimate, add a targeted exception for that rule ID and that path, and log why you added it. A WAF with ten surgical exceptions is stronger than one that someone turned off in frustration.
- Version-control your
.htaccessfile if you edit it by hand. A.htaccessregenerated from Settings → Permalinks is recoverable. A hand-edited one with custom deny rules that got overwritten by a plugin is not. Either commit it, or let a plugin own it so a reinstall restores it. - After any migration, fix permissions explicitly. Check and correct permissions in the hosting panel's file manager (or run the
chmodcommands above over SSH) and confirmwp-config.phpis440or400. A migration that looked successful but left permissions wrong will produce 403s on random assets later, and those are the hardest to find after the fact.
A 403 on a healthy WordPress site is almost always a deliberate decision the server made and forgot to tell you about. The five fixes above track that decision down to the layer that made it.