Short answer: a 403 Forbidden error means the server understood the request but refuses to serve it, and logging in again with the same account will not help. Visitors can check the address, clear the site’s cookies or switch off a VPN. Site owners usually find file permissions, a missing index file, an access rule or a WAF behind it.
If it is your own server, the web server’s error log names the reason in one line. Reload the page once, then read the newest entries:
# From any machine: status line and Server header
curl -sI https://example.com/private/ | grep -iE '^(HTTP|server|cf-ray)'
# On the server: the newest error lines
sudo tail -n 20 /var/log/nginx/error.log # nginx
sudo tail -n 20 /var/log/apache2/error.log # Apache (Rocky, Alma: /var/log/httpd/error_log)What does error 403 mean?
RFC 9110, the current HTTP specification, defines 403 as a server that “understood the request but refuses to fulfill it”. If the request carried credentials, the server considers them insufficient, and the client should not repeat it with the same ones. The RFC also says that a request can be forbidden for reasons that have nothing to do with credentials, and that a server which wants to hide that a resource exists may answer 404 instead.
A 403 is a decision, not a crash: a file the web server cannot read, a directory nobody may list, a rule that blocks your address, or a firewall that disliked the request. When the server sends no page of its own, Chrome shows Access to example.com was denied with HTTP ERROR 403. Otherwise the page tells you who answered:
| What you see | Who sent it | Where to look |
|---|---|---|
White page, 403 Forbidden, footer nginx | nginx | nginx error log |
Forbidden. You don’t have permission to access this resource. with an Apache/2.4 footer | Apache | Apache error log |
| 403 Forbidden. Access to this resource on the server is denied! | LiteSpeed | LiteSpeed error log, .htaccess |
403 - Forbidden: Access is denied or a detailed HTTP Error 403.14 | IIS on Windows Server | IIS section |
| Sorry, you have been blocked or Error 1020 Access denied, with a Cloudflare Ray ID | Cloudflare's WAF or a firewall rule | WAF section |
| The site's own styled page, or JSON from an API | The application | The app's log: plugin, CSRF check, missing permission |
401 vs 403 vs 404: what is the difference?
The three are often confused because all of them keep you from the page. They say different things, and only one of them is fixed by logging in:
| Question | 401 Unauthorized | 403 Forbidden | 404 Not Found |
|---|---|---|---|
| What it says | Who are you? No valid credentials | Understood, and refused | Nothing here, or nothing the server admits to |
| Does logging in help? | Yes, with valid credentials | No, not with the same account | No |
| Required header | WWW-Authenticate | None | None |
| Typical cause on your server | auth_basic, an expired token or session | Permissions, missing index file, deny rule, WAF | Wrong root, a typo, a missing route |
| Who fixes it | The visitor, by authenticating | The site owner, or an owner granting access | Whoever moved or deleted the content |
401 Unauthorized and 404 Not Found have their own guides. Some sites answer 404 for private pages on purpose, as RFC 9110 allows, so a 404 on a page you know exists can be a 403 in disguise.
How do I fix a 403 error as a visitor?
On a site you do not run, most 403s are deliberate and only the owner can lift them. These checks cover the cases you can fix yourself:
- Check the address. A URL that ends at a folder, such as
/images/or/wp-content/uploads/, asks the server to list a directory, and most servers refuse that with a 403. Follow the link from the home page instead. - Log in with the right account. If you are logged in and still get 403, your account lacks the role for that page. A different account, or the owner granting you access, is the fix; logging in again with the same one is not.
- Clear the site’s cookies. A stale session or security cookie can make an app refuse every request. Delete the cookies for that one site, or open it in a private window to test.
- Switch the VPN or proxy off. Sites block address ranges that send abuse, the data centre ranges that VPNs use, and sometimes whole countries for licensing reasons. If the page works on mobile data but not on your VPN, the VPN’s address is blocked. A shared office or school connection can be blocked for the same reason.
- Note the Ray ID on a block page. A Cloudflare Sorry, you have been blocked page means a security rule matched your request, and retrying will not help. Send the site owner the Ray ID and the time; it lets them find the rule.
- Slow down. Some security tools answer a burst of requests, or a scraper’s user agent, with 403 rather than 429 Too Many Requests. Wait a few minutes before trying again.
Site owner: find the cause in the logs
On your own server, start with who answered. If the Server header reads cloudflare, repeat the request straight to the origin with --resolve example.com:443:203.0.113.10 (your server’s IP) to see whether your server or the CDN refused it.
The HTTP header checker shows the status line and headers from outside your network and says when a CDN’s bot protection answered instead of your server.
Then match the newest line in the error log:
| Log line | Server | Cause |
|---|---|---|
directory index of "/var/www/site/" is forbidden | nginx | No index file, listings off |
open() "/var/www/site/index.html" failed (13: Permission denied) | nginx | Permissions or SELinux |
access forbidden by rule | nginx | An allow/deny rule |
AH01630: client denied by server configuration: /srv/site/ | Apache | A Require rule |
AH00035: access to /index.html denied (filesystem path '/srv/site/index.html') because search permissions are missing on a component of the path | Apache | A parent directory without x |
AH01276: Cannot serve directory /srv/site/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive | Apache | No index file, listings off |
ModSecurity: Access denied with code 403 (phase 2) | nginx or Apache with ModSecurity | A WAF rule |
| Nothing in the error log, 403 in the access log | Your application | The app’s own check |
A virtual host can have its own log file (sudo grep -R error_log /etc/nginx/ or ErrorLog in /etc/apache2/ finds it); in Plesk, the domain’s Logs page shows the same lines. Whether the 403 hits everyone or one address, the access log tells you:
# combined log format: the status follows the quoted request
sudo awk -F'"' '$3 ~ /^ 403 / {print $2}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
sudo awk -F'"' '$3 ~ /^ 403 / {split($1, a, " "); print a[1]}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | headThe nginx-specific walk-through, with the try_files trap and the Let’s Encrypt dotfile rule, is in nginx 403 Forbidden. The causes below apply to every web server.
Cause 1: the web server cannot read the files
The web server’s workers run as an unprivileged user, not as you and not as root. That user needs read permission on the file and execute (search) permission on every directory above it. One directory without x for that user is enough for a 403.
| Distribution | nginx | Apache |
|---|---|---|
| Debian, Ubuntu | www-data | www-data |
| Rocky, AlmaLinux, RHEL | nginx | apache |
| Official nginx Docker image | nginx | n/a |
Check rather than assume with ps -o user=,args= -C nginx,apache2,httpd. Then let namei print the permissions of every component on the path at once:
namei -l /var/www/site/index.htmlf: /var/www/site/index.html
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root www
drwxr-x--- deploy deploy site
-rw-r--r-- deploy deploy index.htmlHere site has no permissions for others, so www-data cannot enter it, and the file inside is unreachable however open it is. Apache logs exactly this case as AH00035, and a file that is not readable itself as AH00132 file permissions deny server access. The standard fix gives directories 755 and files 644:
sudo chown -R deploy:deploy /var/www/site
sudo find /var/www/site -type d -exec chmod 755 {} +
sudo find /var/www/site -type f -exec chmod 644 {} +
# only what the app writes (uploads, cache) belongs to the web server user
sudo chown -R www-data:www-data /var/www/site/uploadsSites in a home directory or in /root fail here by default, because those directories are closed to other users (Debian 12’s adduser creates homes with mode 0700); move the site to /var/www or /srv instead of opening your home directory. The chmod guide explains the digits, the chown guide covers ownership after a copy or a migration, and the chmod calculator translates modes both ways.
Cause 2: a directory without an index file
A request for / or /blog/ maps to a directory. The server looks for the index files it is configured with (index in nginx, DirectoryIndex in Apache), and if none exists and directory listings are off, it answers 403. nginx ships with autoindex off, so this is the usual result there. Debian’s Apache allows listings under /var/www (Options Indexes), so you get a file list instead until you switch it off with Options -Indexes; from then on a missing index is a 403 logged as AH01276.
- The document root points at the wrong folder. Laravel serves from
public/, most front-end builds fromdist/orbuild/. - A PHP site without
index.phpin the list. Useindex index.php index.html;in nginx orDirectoryIndex index.php index.htmlin Apache. - The upload did not finish. An empty web root after a failed deploy looks exactly like this.
Do not turn listings on to make the 403 go away. That replaces the error with a public list of every file in the folder, backups and config copies included.
Cause 3: an access rule matches the request
On nginx the log says access forbidden by rule, and sudo nginx -T | grep -nE 'deny|allow' finds the rule. On Apache the message is AH01630, and the most common trigger is a document root outside /var/www. Debian’s apache2.conf denies the whole filesystem with Require all denied on <Directory /> and grants access only to /var/www and /usr/share. A site in /srv/site needs its own grant:
<Directory /srv/site>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>sudo apache2ctl configtest && sudo systemctl reload apache2Other rules that end in a 403:
- An
.htaccessline such asRequire ip 203.0.113.0/24,Require all deniedor the old Apache 2.2 formDeny from all, often added by a security plugin to protect an admin area. The 2.2 directives needmod_access_compat, and Apache logs their refusals as AH01797. - An allow list behind a proxy. Behind Cloudflare or a load balancer, the server sees the proxy’s address, so an allow list for your office never matches. Configure
set_real_ip_fromandreal_ip_headerin nginx, orRemoteIPHeaderandRemoteIPTrustedProxyfrommod_remoteipin Apache, before you rely on the client address. - Hidden files. Many configs block every dotfile. That is right for
.envand.git, and wrong for/.well-known/, which certificate validation needs.
Cause 4: SELinux on Rocky, AlmaLinux and RHEL
If the Unix permissions are right and the log still says Permission denied on a Red Hat family system, check SELinux. It is enforcing by default there and lets the web server read only files labelled for web content, such as httpd_sys_content_t; files moved with mv from a home directory keep their old label.
getenforce # Enforcing?
ls -Zd /srv/site # which label?
sudo ausearch -m avc -ts recent | tail # what SELinux refused
sudo semanage fcontext -a -t httpd_sys_content_t '/srv/site(/.*)?'
sudo restorecon -Rv /srv/siteDirectories the app writes to need httpd_sys_rw_content_t, and sites served from users’ public_html need sudo setsebool -P httpd_enable_homedirs 1. Do not switch SELinux off to fix one label. The nginx 403 guide has more on labels and chcon versus semanage.
Cause 5: a WAF, a CDN rule or the application
When the web server’s own checks pass, a layer that inspects requests refused it. ModSecurity with the OWASP Core Rule Set writes a line like this to the error log, and the full request to its audit log (on Debian /var/log/apache2/modsec_audit.log):
ModSecurity: Access denied with code 403 (phase 2). ... [id "949110"]
[msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [uri "/wp-admin/post.php"]Rule 949110 is only the verdict; the lines just before it name the rules that added to the score. If one of them is a false positive for a known path, remove that rule for the path rather than turning the engine off:
<LocationMatch "^/wp-admin/post\.php$">
SecRuleRemoveById 942100
</LocationMatch>- Cloudflare. A request that got a Cloudflare block page never reached your server. Filter the security events in the Cloudflare dashboard by the visitor’s Ray ID; the event shows the rule, the country and the address that matched.
- The application. Django answers a failed CSRF check with a 403 page that reads CSRF verification failed. Request aborted., WordPress security plugins block by country or user agent, and APIs return 403 for a valid token that lacks a scope. The access log shows the 403 and the error log stays silent, so read the app’s log.
- User agent filters. Some rules block
curlorpython-requestsby name, so a test from the shell gets 403 while browsers do not. Compare withcurl -sI -A 'Mozilla/5.0' URL.
Hotlink protection
Images or downloads that work on your site but fail when embedded elsewhere are usually hotlink protection: a rule that answers 403 when the Referer header names another site. In nginx it looks like this:
location ~* \.(jpe?g|png|gif|webp)$ {
valid_referers none blocked server_names *.example.com;
if ($invalid_referer) {
return 403;
}
}curl -sI -e https://other.example/ https://example.com/img/photo.jpg sends a foreign referer and shows whether the rule fires. If your own pages are refused, a new domain or CDN hostname is missing from the list.
403 on Windows Server and IIS
IIS adds a substatus. The one people meet most is HTTP Error 403.14 - Forbidden: the request maps to a folder without a default document while directory browsing is off, the IIS version of cause 2. Add the start page under Default Document in IIS Manager, or fix the site’s physical path. Two other substatuses are common: 403.4 when the site requires HTTPS and the request came over plain HTTP, and 403.503 when IP and Domain Restrictions rejected the client’s address. Missing NTFS read rights for the application pool identity or IUSR, by contrast, usually show up as 401.3, not as a 403. On a Windows VPS the detailed error page is shown only on the server itself, so reproduce the request there.
A custom 403 page, or a 404 instead
A branded 403 page is one line: error_page 403 /403.html; in nginx, or ErrorDocument 403 /403.html in Apache. Keep the status 403, make the page readable by the web server (otherwise the error page itself fails), and tell people what to do next: whom to contact, or how to log in with the right account. For admin areas you would rather not advertise, answering 404 is allowed by RFC 9110 and gives scanners less to work with.
Running the site on your own server
On a VPS or a dedicated server, every layer that can say 403 is yours, which is why the fix is quick once the log line is in front of you. Deploy as a normal user rather than root, so files never end up owned by an account the web server cannot read, and test every config before reloading. If you would rather not manage users and permissions per site at all, Plesk hosting sets up each site’s user and permissions for you.
Frequently asked
What does error 403 mean?
The server understood the request but refuses to fulfil it. RFC 9110 defines 403 Forbidden that way, and unlike a 401, sending the same credentials again will not change the answer. The usual reasons are file permissions, a directory without an index file, an access rule, a blocked IP address or country, or a web application firewall.
What is the difference between 401 and 403?
401 Unauthorized means the request carried no valid credentials, and logging in fixes it; the response must include a WWW-Authenticate header. 403 Forbidden means the server refuses the request anyway, so logging in again with the same account does not help. You need other rights, another account or a change on the server.
How do I fix a 403 Forbidden error on my own website?
Read the web server's error log right after the error. Permission denied means the web server user cannot read a file or pass through a directory: use 644 for files and 755 for directories. 'directory index of ... is forbidden' or AH01276 means there is no index file. 'access forbidden by rule' or AH01630 means an allow/deny or Require rule. On Rocky Linux or AlmaLinux, also check the SELinux labels.
Can a VPN cause a 403 error?
Yes. Many sites and firewalls block the address ranges that VPNs and data centres use, and some block whole countries. If a page loads on mobile data but returns 403 through the VPN, the VPN's address is blocked; switch the VPN off for that site or ask the owner to allow it.