Guides

9 min readUpdated

403 Forbidden: what it means, who sent it and how to fix it

Quick answer

A 403 Forbidden error means the server understood the request and refused to carry it out. Signing in again rarely helps, because a rule caused the refusal, not a missing password. That rule can sit on the origin web server (file permissions, a missing index file, an allow or deny line), in the application, or in a WAF or CDN in front of it. Find which layer sent the 403 from the response, then fix that layer.

01What does 403 Forbidden mean?

RFC 9110 defines 403 as the status a server returns when it "understood the request but refuses to fulfill it". If credentials were sent, the server considers them insufficient, and the client should not simply repeat the request with the same ones. The standard also lets a server answer 404 instead when it would rather not admit that the resource exists, so a 404 on a path you know is there can be a hidden 403.

A 401 Unauthorized asks the client to authenticate; a 403 says that authenticating again will not change the answer, and the 401 page compares the two in detail. Nor is 403 a rate limit: a throttling server should send 429 Too Many Requests, although some firewalls answer 403 to both. The HTTP status codes guide shows which layer usually sends each code.

02Which layer returned the 403?

The same code comes from at least five places, and the response usually gives the layer away: a default page names the server software, a WAF or CDN block page carries a reference or ray ID, and an application returns its own page or JSON. Provider codes are collected in the Cloudflare error codes guide.

Then compare the public hostname with the origin: 403 at the edge and 200 at the origin means the edge is blocking; 403 at both means the origin or the application is. Some firewalls refuse command-line tools, so confirm a curl result in a browser's developer tools.

Who sent itWhat the response looks likeTypical causesFirst check
Origin server (nginx)A plain 403 Forbidden page with nginx in the footerUnreadable file, no index file, allow or deny ruleerror.log, same second
Origin server (Apache)"Forbidden. You don't have permission to access this resource."Require rule, no index file, permissionsThe error.log IDs in the Apache section below
ApplicationA branded page or JSON; Django says "CSRF verification failed. Request aborted."Missing role, failed CSRF or origin check, wrong API scopeThe application log
WAF or CDN edgeA branded block page with a reference or ray ID; Cloudflare's error 1020 "Access denied" carries status 403Managed or custom rule, IP reputation, country or bot ruleThe security event log, searched by that ID
Storage origin behind a CDNA storage service error instead of your pagePrivate bucket without access for the CDN, wrong object keyThe object through the CDN and directly
Hotlink protection403 only for files embedded on other sitesReferer not on the allowed listThe request with and without a Referer
Find out who answered (replace example.com, the path and the origin address 203.0.113.10)
# status, Server and any provider headers from the public hostname
curl -sI https://example.com/private/report.pdf

# the first lines of the body: default server page, block page or app page?
curl -s https://example.com/private/report.pdf | head -n 20

# compare: the same request sent directly to your own origin server
curl -sI --resolve example.com:443:203.0.113.10 https://example.com/private/report.pdf

03403 troubleshooting by symptom

Who gets the 403, and on which URLs, narrows the cause faster than any single setting. Each row names the layer that most often generates it.

SymptomGenerated byLikely causeTestFix
Every page fails after a deployOriginWrong owner or mode, or a parent directory the server cannot enternamei -l; test -r as the server userDirectories 755, files 644
Only a directory URL such as /docs/ failsOriginNo index file, directory listings offIs there an index.html or index.php?Add it, or fix the index setting
Only /admin fails, even from the allowed office networkOrigin or WAFAn address allow list that sees the proxy's or CDN's address, not the visitor'sThe client address in the access logRestore the real client address first (nginx real IP)
Forms fail, page views workApplicationCSRF or Origin check, often after adding a proxyThe application logFix trusted origins and the Host header (CSRF explained)
Only some visitors, often on VPNs or abroadWAF or CDNIP reputation, country or bot ruleThe reference ID in the security logNarrow the rule for that path
Images break when embedded on other sitesOrigin or CDNHotlink protectioncurl with and without -e (Referer)Allow the legitimate referrers
Long or unusual query strings failWAFA managed rule matched an injection-like patternThe WAF event names the ruleExclude that parameter from that rule

04What does the nginx error log say about a 403?

nginx logs the reason in the same second as the 403, by default in /var/log/nginx/error.log on Debian and Ubuntu. The lines below were reproduced on nginx 1.22.1 with workers running as www-data; paths are shortened.

A deliberate return 403, as in hotlink rules, leaves no error-log line, so if the error log is silent, search the configuration for return 403 and deny. Permission errors are often on a directory above the file, which namei -l shows in one command.

Error log line containsCauseFix
directory index of "/var/www/site/docs/" is forbiddenA directory URL, none of the files in the index directive exist, and autoindex is off (the default)Add the index file, or name it: index index.html index.php
open() "/var/www/site/report.html" failed (13: Permission denied)The worker cannot read the file, or cannot enter a directory above itDirectories need execute, files read permission for the worker user
access forbidden by ruleAn allow or deny directive matched, including a dotfile ruleRead the matching location; behind a proxy, restore the real client address first
Diagnose and fix the three common nginx causes (run on nginx 1.22.1 with nginx -t and live requests, Debian 12)
# what does the worker user see along the path?
namei -l /var/www/site/private/page.html
sudo -u www-data test -r /var/www/site/private/page.html && echo readable

# reset permissions under the document root (never 777)
find /var/www/site -type d -exec chmod 755 {} +
find /var/www/site -type f -exec chmod 644 {} +

# nginx: index files, an admin allow list and a dotfile rule
server {
    root  /var/www/site;
    index index.html index.php;

    location /admin/ {
        allow 192.0.2.0/24;    # office range
        deny  all;
    }

    location ~ /\.(?!well-known/) { deny all; }   # .git, .env; keeps ACME challenges working
}

05How do I fix a 403 Forbidden in Apache?

Apache 2.4 tags each reason with a message ID; on Debian and Ubuntu the log is /var/log/apache2/error.log. The IDs below were reproduced on Apache 2.4.68.

After an upgrade from 2.2, old access syntax is the usual cause. Require replaced Order, Allow and Deny: Deny from all became Require all denied, and Allow from all became Require all granted. Old lines still work through mod_access_compat and log AH01797 when they deny, but the upgrade guide warns against mixing the two styles. An .htaccess file in any parent directory can add rules of its own.

Error log IDMessageFix
AH01630client denied by server configurationA Require rule denied it: Require all granted for public content, or correct the address rule
AH01797client denied by server configuration (mod_access_compat)An old Order, Allow or Deny line denied it: replace it with Require
AH01276Cannot serve directory: No matching DirectoryIndex found, and server-generated directory index forbidden by Options directiveAdd the index file or extend DirectoryIndex; keep Options -Indexes
AH00035access denied because search permissions are missing on a component of the pathGive the Apache user execute permission on every parent directory
AH00132file permissions deny server accessMake the file readable by the Apache user
Apache 2.4 equivalent of the nginx block (apache2 -t and live requests on Apache 2.4.68, Debian 12)
grep -E 'AH01630|AH01797|AH01276|AH00035|AH00132' /var/log/apache2/error.log | tail -n 20

<Directory /var/www/site>
    Require all granted
    DirectoryIndex index.html index.php
    Options -Indexes
    # ignore .htaccess files unless the site relies on them
    AllowOverride None
</Directory>

<Location /admin/>
    Require ip 192.0.2.0/24
</Location>

06What if the 403 comes from a WAF or CDN?

An edge returns 403 when one of its rules stops a request before the origin: a managed rule, a custom rule, a country or address block, or a bot rule. The origin never sees these requests, so its logs stay empty and nothing changed there will help.

Take the reference ID from the block page, find the event in the provider's security log and read which rule fired. For a false positive, narrow the rule for that path or parameter instead of switching it off. CloudFront's documentation notes that when its WAF is in use, it cannot tell a WAF 403 from an origin 403, so check the WAF log first.

Behind a proxy, the origin sees the proxy's addresses unless it restores the visitor's from a trusted header, so an office allow list can lock everyone out. The X-Forwarded-For guide explains which header to trust.

  • A WAF can refuse attack patterns, blocked countries or networks and rule violations before they cost the origin anything
  • A WAF can show the owner which rule blocked a request, the fastest way to settle a false positive
  • A WAF cannot fix a 403 the origin sends for permissions, index files or roles
  • A WAF cannot decide which users may see a page; that decision stays in the application

07Do 403 errors hurt SEO?

They can, if search engines receive them. Google's crawling documentation says URLs that return a 4xx code are not indexed and indexed ones are removed, and it advises against using 401 or 403 to slow crawling, because 4xx codes other than 429 have no effect on crawl rate. Make sure firewall and bot management rules let verified search crawlers through.

Caches rarely keep a 403. RFC 9110 does not list it among the heuristically cacheable codes, so a shared cache stores one only when the response explicitly allows it, for example with Cache-Control: max-age. Some CDNs apply their own defaults to error codes, so check the provider's documentation if a fixed 403 seems to linger.

Questions

Frequently asked questions

The server understood the request but refuses to carry it out. The refusal is deliberate: a file permission, an access rule, an application role or a firewall rule decided the request should not be served. Unlike a 401, sending credentials again will not change the answer, and unlike a 5xx error nothing has crashed. The site has to change the rule, or confirm the refusal is intended.

Usually not. Most 403s come from the site's configuration or its firewall. Check the URL, sign in again, and try your normal connection instead of a VPN, proxy or company network, since some sites block those addresses. If the page still shows 403, send the site owner the time, the address and any reference ID on the page, which lets them find the exact rule.

A 404 says the server has nothing at that address; a 403 says it has something and will not serve it to you. The HTTP standard lets a server answer 404 instead of 403 when it does not want to reveal that a resource exists, so private areas often return 404 on purpose. When troubleshooting, treat a 404 on a path you know exists as a possible hidden 403.

The server found the directory but no index file to serve, and automatic directory listings are switched off, which is the safe default in both nginx and Apache. Add an index.html or index.php, or make sure the server's index setting names the file you use. Do not switch directory listings on to make the error go away, because that exposes every file in the folder.

Yes. A rule written for attack patterns can match a legitimate search query, form field or cookie, and address or country rules can catch people on shared networks or VPNs. That is a false positive. The owner should look the request up by its reference ID, see which rule matched and narrow that rule for the affected path or parameter, rather than switching protection off for the whole site.

Sources

Sources and verification

nginx causes, log lines and fixes reproduced on nginx 1.22.1 (nginx -t and live curl 7.88.1 requests, workers running as www-data, in isolated network and mount namespaces), including the dotfile rule with its /.well-known/ exception. Apache causes, log IDs (AH01630, AH01797, AH01276, AH00035, AH00132), the default 403 page and the fixed configuration reproduced on Apache 2.4.68 from the Debian package (apache2 -t and live requests), as was the syntax error caused by a comment after a directive. namei (util-linux 2.38.1), GNU find 4.9.0 with chmod, and sudo 1.9.13 run on the same test tree. Debian 12, October 2026.

  1. 01RFC 9110, HTTP Semantics, section 15.5.4: 403 Forbidden · RFC Editor
  2. 02RFC 9110, section 15.1: heuristically cacheable status codes · RFC Editor
  3. 03Module ngx_http_access_module (allow, deny) · nginx.org
  4. 04Module ngx_http_index_module · nginx.org
  5. 05Module ngx_http_referer_module (valid_referers) · nginx.org
  6. 06Upgrading to 2.4 from 2.2: access control changes · Apache HTTP Server Project
  7. 07Apache Module mod_authz_core (Require) · Apache HTTP Server Project
  8. 08Apache Module mod_dir (DirectoryIndex) · Apache HTTP Server Project
  9. 09Django: Cross Site Request Forgery protection · Django
  10. 10HTTP 403 status code (Permission Denied) · AWS CloudFront docs
  11. 11Error 1020: Access denied · Cloudflare docs
  12. 12How HTTP status codes affect Google's crawlers · Google Search Central

Last updated