HTTP 403 Forbidden Error: 8 Ways to Fix
The 403 Forbidden error means the server understood the request but refuses to fulfill it. Unlike 401 (unauthenticated), 403 says: "I know who you are, but you have no access." It is a server-side access policy — on files, directories, or specific URLs.
We review 8 real causes of 403 and practical fixes for nginx, Apache, WordPress, and how to detect blocks by ModSecurity, Cloudflare WAF, or rate limiters.
What 403 Means
Per RFC 9110 §15.5.4, 403 indicates the server understands the request but refuses to authorize it. Re-authentication will not help — the decision is server-side (ACL, permissions, policy).
8 Common Causes
- Wrong file permissions — 600, 640 instead of 644.
- File owner is not the web server user (root:root instead of www-data:www-data).
- No index file in the directory and directory listing disabled.
- Block in .htaccess or nginx (
deny all). - ModSecurity or WAF rule blocked a suspicious request.
- IP block via Cloudflare, fail2ban, or firewall.
- SELinux/AppArmor denies web server access to the file.
- Hotlink protection — Referer does not match the allowlist.
Diagnosis
Always start with logs. 403 is a server code, and the reason is there.
# nginx
tail -50 /var/log/nginx/error.log | grep -i "forbidden\|403"
# Apache
tail -50 /var/log/apache2/error.log
tail -50 /var/log/apache2/access.log | grep " 403 "
# ModSecurity
tail -50 /var/log/modsec_audit.log
# Check permissions
ls -la /var/www/html/problem-file
# Expected: -rw-r--r-- 1 www-data www-dataUse the HTTP Header Checker for the external response and Security Scanner to check whether the server leaks information through headers.
8 Solutions
1. Fix file permissions
# Files
find /var/www/html -type f -exec chmod 644 {} \;
# Directories
find /var/www/html -type d -exec chmod 755 {} \;
# Owner
chown -R www-data:www-data /var/www/html2. Verify index file
# nginx
index index.php index.html;
# Apache
DirectoryIndex index.php index.html3. Find blocks in .htaccess
# Look for:
Deny from all
Require all denied
# Replace with:
Require all granted4. nginx: deny directives
# Problematic:
location ~ \.(env|git|htaccess)$ {
deny all;
}
# If too broad — review the pattern5. ModSecurity false positive
# Identify the rule from the log:
# [id "949110"] [msg "Inbound Anomaly Score Exceeded"]
# Disable for a specific URL
SecRule REQUEST_URI "@beginsWith /api/safe-endpoint" \
"id:1001,phase:1,ctl:ruleRemoveById=949110,nolog,allow"6. Check IP blocks
# fail2ban
fail2ban-client status
fail2ban-client unban YOUR_IP
# iptables
iptables -L -n | grep DROP
iptables -D INPUT -s BLOCKED_IP -j DROP7. SELinux context (CentOS/RHEL)
ls -Z /var/www/html/
# Should be httpd_sys_content_t
restorecon -Rv /var/www/html/
chcon -t httpd_sys_content_t /var/www/html/newfile.php8. WordPress
- Rename
.htaccess, visit Settings → Permalinks to regenerate. - Review security plugins (Wordfence, iThemes) — they may have blocked your IP.
chmod 644 wp-config.php,chmod 755 wp-content/
403 on /admin or /wp-admin
If the site works but admin returns 403:
- Check IP allowlist in
.htaccessor nginx. - Review Cloudflare Firewall Rules on the admin path.
- Check fail2ban — your IP may be banned after failed logins.
- Verify 2FA — some plugins return 403 on wrong TOTP.
Frequently Asked Questions
Q: What is the difference between 401 and 403?
A: 401 = "authenticate" (no credentials). 403 = "you have no access" (credentials exist but insufficient rights).
Q: After scp the file returns 403, why?
A: The default owner is root. Run chown www-data:www-data file && chmod 644 file.
Q: How do I tell 403 from the server vs from a WAF?
A: Check the Server: and cf-ray headers. Cloudflare usually shows its own branded page with an ID. The Enterno.io HTTP Checker shows all headers.
Q: 403 for Googlebot — is that bad?
A: Critical. Google cannot index the page. Review robots.txt and WAF — make sure Googlebot (official IP list) is not blocked.
Conclusion
403 is a conversation about access rights. Start with logs, verify file permissions and ownership, hunt for deny rules in .htaccess and nginx, rule out WAF and IP blocks. Configure monitoring with expected_code=200 to catch 403 regressions instantly.
Check your website right now
Check now →