HTTP 403 Forbidden
The server understood the request but refuses to authorize it — you (or the server process) are not allowed to access this resource.
Meaning
A 403 means access is denied on purpose. Unlike 401, logging in again usually won’t help: either your account lacks permission, or the server itself is configured to refuse the request (file permissions, IP rules, a WAF, a missing index file).
On web servers the most common sources are file-system permissions, directory listing being disabled with no index file, and deny rules. On APIs it usually means your token is valid but lacks the required scope or role.
Common causes
- The authenticated user/token lacks the required role, scope or permission
- File or directory permissions don’t let the web server user (
www-data,nginx,apache) read the file - Directory requested with no
index.html/index.phpand directory listing disabled deny/Require all deniedrules, IP allow-lists or geo-blocking- Firewall / WAF (Cloudflare, ModSecurity, AWS WAF) blocking the request pattern or user agent
- CSRF token missing or invalid on a form POST (many frameworks answer 403)
- Hotlink protection or a missing/blocked Referer
- SELinux context preventing the web server from reading files
⚡ Quick fix
- Check the server error log — it usually says exactly why (permissions, index, rule)
- Make files readable by the web server:
chmod 644files,chmod 755directories - Add an index file or the right
index/DirectoryIndexdirective - Remove or adjust the deny / IP rule that matches your request
- For APIs: confirm the token has the needed scope/role, not just that it’s valid
- Check WAF / Cloudflare firewall events for a blocked request
Detailed fix by platform
Nginx
- Look in
/var/log/nginx/error.logfor "permission denied", "directory index … is forbidden" or "access forbidden by rule". - Ensure an index exists and the root is readable:nginx
server { root /var/www/app/public; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } } - Fix ownership:
sudo chown -R www-data:www-data /var/www/app(user may benginxon RHEL). - Every parent directory needs execute (
x) permission for the nginx user.
Apache
- Check
/var/log/apache2/error.log(orhttpd/error_log) for "AH01630: client denied by server configuration" or "AH01276: Cannot serve directory". - Allow access in the vhost or .htaccess:apache
<Directory /var/www/app/public> Options -Indexes +FollowSymLinks AllowOverride All Require all granted </Directory> DirectoryIndex index.php index.html - Search
.htaccessfiles forDeny from,Require iporRewriteRule … [F].
PHP
- If your app returns 403 itself, log the authorization decision (user id, role, required permission).
- For CSRF-protected forms, make sure the token is rendered in the form and the session cookie is sent.
WordPress
- Temporarily rename the
pluginsfolder to rule out security plugins (Wordfence, iThemes). - Regenerate
.htaccessby re-saving Settings → Permalinks. - Reset permissions: directories 755, files 644,
wp-config.php640.
REST API
- Decode the token and verify
scope/rolesinclude what the endpoint needs. - Check the API’s IP allow-list and per-key restrictions (allowed referrers, apps).
- Return 401 for bad/missing credentials and 403 only for valid-but-insufficient ones.
Cloud
- AWS S3/CloudFront: check bucket policy, Block Public Access and Origin Access Control (S3 returns 403 for missing objects when you lack ListBucket).
- Cloudflare: Security → Events shows which WAF rule blocked the request.
Code examples
Reproduce and see the reason
curl -I https://example.com/api/users
sudo tail -n 50 /var/log/nginx/error.log
namei -l /var/www/app/public/index.php # shows permissions of every path segmentnamei -l quickly reveals a parent directory the web server can’t traverse.
Handle 403 differently from 401 in the client
const res = await fetch('/api/data', { headers: { Authorization: `Bearer ${token}` } });
if (res.status === 401) return redirectToLogin(); // not authenticated
if (res.status === 403) return showError('You do not have access to this data.'); // authenticated but not allowedHow to diagnose
- Request — Correct URL, method and host? Any unusual headers or user agent?
- Authentication — Is the user/token recognized at all? (If not, expect 401.)
- Authorization — Does this user/token have the role or scope for this resource?
- Server — File permissions, index file, deny rules — check the server error log
- Firewall — WAF, CDN or IP rules blocking the request?
- Application — Does app code (CSRF, middleware, policies) return 403?
🧠 Still stuck? Analyze your error
Paste the full message, response headers or stack trace — we'll detect the platform and point to the most likely cause.
Report a correction or suggest an improvement
Last updated 2 Oct 2026