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 it | What the response looks like | Typical causes | First check |
|---|---|---|---|
| Origin server (nginx) | A plain 403 Forbidden page with nginx in the footer | Unreadable file, no index file, allow or deny rule | error.log, same second |
| Origin server (Apache) | "Forbidden. You don't have permission to access this resource." | Require rule, no index file, permissions | The error.log IDs in the Apache section below |
| Application | A branded page or JSON; Django says "CSRF verification failed. Request aborted." | Missing role, failed CSRF or origin check, wrong API scope | The application log |
| WAF or CDN edge | A branded block page with a reference or ray ID; Cloudflare's error 1020 "Access denied" carries status 403 | Managed or custom rule, IP reputation, country or bot rule | The security event log, searched by that ID |
| Storage origin behind a CDN | A storage service error instead of your page | Private bucket without access for the CDN, wrong object key | The object through the CDN and directly |
| Hotlink protection | 403 only for files embedded on other sites | Referer not on the allowed list | The request with and without a Referer |
# 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.pdf03403 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.
| Symptom | Generated by | Likely cause | Test | Fix |
|---|---|---|---|---|
| Every page fails after a deploy | Origin | Wrong owner or mode, or a parent directory the server cannot enter | namei -l; test -r as the server user | Directories 755, files 644 |
| Only a directory URL such as /docs/ fails | Origin | No index file, directory listings off | Is there an index.html or index.php? | Add it, or fix the index setting |
| Only /admin fails, even from the allowed office network | Origin or WAF | An address allow list that sees the proxy's or CDN's address, not the visitor's | The client address in the access log | Restore the real client address first (nginx real IP) |
| Forms fail, page views work | Application | CSRF or Origin check, often after adding a proxy | The application log | Fix trusted origins and the Host header (CSRF explained) |
| Only some visitors, often on VPNs or abroad | WAF or CDN | IP reputation, country or bot rule | The reference ID in the security log | Narrow the rule for that path |
| Images break when embedded on other sites | Origin or CDN | Hotlink protection | curl with and without -e (Referer) | Allow the legitimate referrers |
| Long or unusual query strings fail | WAF | A managed rule matched an injection-like pattern | The WAF event names the rule | Exclude 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 contains | Cause | Fix |
|---|---|---|
| directory index of "/var/www/site/docs/" is forbidden | A 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 it | Directories need execute, files read permission for the worker user |
| access forbidden by rule | An allow or deny directive matched, including a dotfile rule | Read the matching location; behind a proxy, restore the real client address first |
# 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 ID | Message | Fix |
|---|---|---|
| AH01630 | client denied by server configuration | A Require rule denied it: Require all granted for public content, or correct the address rule |
| AH01797 | client denied by server configuration (mod_access_compat) | An old Order, Allow or Deny line denied it: replace it with Require |
| AH01276 | Cannot serve directory: No matching DirectoryIndex found, and server-generated directory index forbidden by Options directive | Add the index file or extend DirectoryIndex; keep Options -Indexes |
| AH00035 | access denied because search permissions are missing on a component of the path | Give the Apache user execute permission on every parent directory |
| AH00132 | file permissions deny server access | Make the file readable by the Apache user |
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
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.
- 01RFC 9110, HTTP Semantics, section 15.5.4: 403 Forbidden · RFC Editor
- 02RFC 9110, section 15.1: heuristically cacheable status codes · RFC Editor
- 03Module ngx_http_access_module (allow, deny) · nginx.org
- 04Module ngx_http_index_module · nginx.org
- 05Module ngx_http_referer_module (valid_referers) · nginx.org
- 06Upgrading to 2.4 from 2.2: access control changes · Apache HTTP Server Project
- 07Apache Module mod_authz_core (Require) · Apache HTTP Server Project
- 08Apache Module mod_dir (DirectoryIndex) · Apache HTTP Server Project
- 09Django: Cross Site Request Forgery protection · Django
- 10HTTP 403 status code (Permission Denied) · AWS CloudFront docs
- 11Error 1020: Access denied · Cloudflare docs
- 12How HTTP status codes affect Google's crawlers · Google Search Central
Last updated