403 🌐 HTTP

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.php and directory listing disabled
  • deny / Require all denied rules, 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

  1. Check the server error log — it usually says exactly why (permissions, index, rule)
  2. Make files readable by the web server: chmod 644 files, chmod 755 directories
  3. Add an index file or the right index / DirectoryIndex directive
  4. Remove or adjust the deny / IP rule that matches your request
  5. For APIs: confirm the token has the needed scope/role, not just that it’s valid
  6. Check WAF / Cloudflare firewall events for a blocked request

Detailed fix by platform

Nginx

  1. Look in /var/log/nginx/error.log for "permission denied", "directory index … is forbidden" or "access forbidden by rule".
  2. 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;
        }
    }
  3. Fix ownership: sudo chown -R www-data:www-data /var/www/app (user may be nginx on RHEL).
  4. Every parent directory needs execute (x) permission for the nginx user.

Apache

  1. Check /var/log/apache2/error.log (or httpd/error_log) for "AH01630: client denied by server configuration" or "AH01276: Cannot serve directory".
  2. 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
  3. Search .htaccess files for Deny from, Require ip or RewriteRule … [F].

PHP

  1. If your app returns 403 itself, log the authorization decision (user id, role, required permission).
  2. For CSRF-protected forms, make sure the token is rendered in the form and the session cookie is sent.

WordPress

  1. Temporarily rename the plugins folder to rule out security plugins (Wordfence, iThemes).
  2. Regenerate .htaccess by re-saving Settings → Permalinks.
  3. Reset permissions: directories 755, files 644, wp-config.php 640.

REST API

  1. Decode the token and verify scope/roles include what the endpoint needs.
  2. Check the API’s IP allow-list and per-key restrictions (allowed referrers, apps).
  3. Return 401 for bad/missing credentials and 403 only for valid-but-insufficient ones.

Cloud

  1. AWS S3/CloudFront: check bucket policy, Block Public Access and Origin Access Control (S3 returns 403 for missing objects when you lack ListBucket).
  2. Cloudflare: Security → Events shows which WAF rule blocked the request.

Code examples

Reproduce and see the reason

bash
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 segment

namei -l quickly reveals a parent directory the web server can’t traverse.

Handle 403 differently from 401 in the client

javascript
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 allowed

How to diagnose

  1. Request — Correct URL, method and host? Any unusual headers or user agent?
  2. Authentication — Is the user/token recognized at all? (If not, expect 401.)
  3. Authorization — Does this user/token have the role or scope for this resource?
  4. Server — File permissions, index file, deny rules — check the server error log
  5. Firewall — WAF, CDN or IP rules blocking the request?
  6. 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.