A 403 Forbidden means the server understood your request and refused to fulfill it. It is not a credentials problem – that would be a 401 Unauthorized. With a 403, the server knows who you are (or does not care) and is deliberately denying access. The resource exists, the URL is correct, but something in the server’s configuration says you are not allowed to reach it.
This is one of the most common HTTP errors and one of the most frustrating because the response rarely tells you why access was denied. The fix depends entirely on which layer generated the 403 and what rule triggered it.
Quick reference#
| Symptom | Likely cause | Fix |
|---|---|---|
| 403 on a newly deployed or migrated site | File permissions too restrictive |
chmod 755
dirs,
chmod 644
files |
403 on a URL ending in
/
with no file specified | Directory listing disabled | Add an index file or request a specific file |
403 on Apache, rules in
.htaccess
|
.htaccess
deny rule | Remove or adjust the deny rule |
| 403 with Nginx error log showing “Permission denied” | File permissions or Nginx deny directive | Fix ownership/permissions or adjust
allow
/
deny
|
| 403 with Cloudflare branding or Ray ID in response | Cloudflare firewall rule or bot protection | Check Security Events in Cloudflare dashboard |
| 403 after triggering a security rule (SQL/XSS pattern) | WAF (ModSecurity) blocking the request | Check audit log, disable the specific false-positive rule |
| 403 from one network but not another | IP-based ban (Fail2ban, firewall, blocklist) | Unban the IP or add a firewall exception |
| 403 from Google API with “does not have permission” | Missing IAM role or API scope | Grant the required permission in Google Cloud console |
| 403 in WordPress admin or on WP pages | WordPress-specific issue | See WordPress 403 guide |
403 vs 401 - the difference matters#
These two errors get confused constantly, and the distinction is important for diagnosing the problem.
A 401 Unauthorized means authentication failed. Either no credentials were sent or the credentials were rejected. The server is saying “I do not know who you are – prove your identity.” Sending valid credentials fixes a 401.
A 403 Forbidden means authorization failed. The server may know exactly who you are and still refuse. Sending credentials again will not help. The server is saying “I know who you are, and the answer is no.”
In practice:
- Getting a login prompt or “authentication required” message = 401 territory
- Getting “access denied” or “forbidden” with no prompt = 403 territory
- A 401 invites you to try again with credentials. A 403 is a final refusal.
Some servers return 403 instead of 401 to avoid revealing that a resource exists. If a file is behind authentication and the server returns 401, an attacker knows the file is there. Returning 403 (or even 404) hides that information.
What the HTTP specification says#
HTTP 403 is defined in RFC 9110, Section 15.5.4. The specification says the server understood the request but refuses to fulfill it. The server may explain the reason in the response body, but it is not required to. Re-authenticating will not help – the refusal is tied to the request itself, not to the credentials.
HTTP/1.1 403 Forbidden
Content-Type: text/html
Server: nginx
The specification explicitly notes that a 403 is not about authentication. If credentials could fix the problem, the correct response would be 401.
Common causes#
File permissions
On Linux servers, every file and directory has permissions that control who can read, write, and execute it. The web server process (usually running as
www-data
,
nginx
,
apache
, or a per-user account) needs read access to serve a file. If the file permissions are too restrictive, the server returns 403.
This is the most common cause on newly deployed or migrated sites. Typical correct permissions:
# Directories need execute permission to be traversable
chmod 755 /var/www/html
chmod 755 /var/www/html/subdir
# Files need read permission for the web server
chmod 644 /var/www/html/index.html
chmod 644 /var/www/html/style.css
If a file is owned by root and the web server runs as
www-data
, the “other” permission bits must allow reading. If a file has
600
permissions (owner read/write only), the web server cannot read it and returns 403.
Check permissions with:
ls -la /path/to/file
Look at both the file and every directory in the path. A 403 can come from a restricted parent directory even if the file itself has correct permissions.
Directory listing disabled
When a URL points to a directory (like
https://example.com/images/
) and no index file exists in that directory, the server has two options: show a directory listing or return 403.
Most production servers disable directory listings for security:
# Nginx
autoindex off; # This is the default
# Apache
Options -Indexes
If you are getting a 403 on a URL that ends with
/
and no specific file, this is likely the cause. The fix is either to add an index file or to request a specific file within that directory.
.htaccess deny rules
On Apache servers,
.htaccess
files can deny access to specific paths, IPs, or request patterns:
# Block all access to a directory
Deny from all
# Block specific IPs
Order Allow,Deny
Allow from all
Deny from 192.168.1.100
# Apache 2.4+ syntax
Require all denied
Require ip 10.0.0.0/8
If you are getting 403 on an Apache server, check every
.htaccess
file in the path from the document root to the requested file. A
.htaccess
in a parent directory applies to all subdirectories.
Search for deny rules:
find /var/www -name ".htaccess" -exec grep -l -i "deny\|require" {} \;
Nginx access restrictions
Nginx uses
deny
and
allow
directives to control access:
location /admin/ {
allow 10.0.0.0/8;
deny all;
}
location ~ /\.ht {
deny all;
}
These are commonly used to protect sensitive paths like admin panels, configuration files, and hidden directories. If your IP is not in the allow list, you get a 403.
Check the Nginx configuration for the site:
nginx -T | grep -A5 "deny\|allow"
Nginx also returns 403 when it cannot read a file due to permissions, identical to the file permissions issue described above. The error log will say “open() failed (13: Permission denied).”
ModSecurity or WAF blocking
Web application firewalls inspect every request for malicious patterns. If a request URL, header, cookie, or body matches a rule, the WAF returns 403 before the request reaches the application.
Common triggers:
- SQL-like patterns in query strings (
?id=1 OR 1=1) - Script tags or JavaScript in form submissions (XSS patterns)
- Path traversal patterns (
../../etc/passwd) - Unusual user agents or missing headers
- Requests to known exploit paths
ModSecurity logs blocked requests. Check the audit log:
tail -50 /var/log/modsec_audit.log
Each entry includes the rule ID that triggered the block. If the rule is a false positive, you can disable it specifically:
SecRuleRemoveById 942100
Do not disable the entire WAF to fix a single 403. Identify the specific rule and either disable that rule, adjust the request that triggers it, or whitelist the specific path.
Cloudflare or CDN blocking
If your site is behind Cloudflare or another CDN, the 403 might come from the CDN rather than your origin server. Cloudflare can block requests based on:
- Firewall rules (IP-based, country-based, ASN-based)
- Bot Fight Mode challenges that fail
- Security level settings blocking low-reputation IPs
- Hotlink protection blocking requests without a valid referer
If the 403 response page has Cloudflare branding or a Ray ID, the block is at the Cloudflare level, not your server.
Check Cloudflare’s Security Events in the dashboard to see what rule triggered the block. The event log shows the matching rule, the blocked IP, and the request details.
IP-based blocking
Multiple layers can block by IP address:
- Fail2ban banning IPs after repeated failed logins
- Server-level firewall rules (iptables, firewalld, nftables)
- Application-level IP blocklists
- Hosting infrastructure bot detection flagging suspicious traffic
If you can access the site from one network but not another, IP-based blocking is the likely cause. Try accessing from a different network or device to confirm.
Google “your client does not have permission”
A 403 from Google services (APIs, Cloud Storage, OAuth endpoints) with the message “your client does not have permission to get URL” has specific causes:
- The Google Cloud IAM role does not include the required permission
- The service account key is valid but lacks the scope for the requested resource
- The API has not been enabled in the Google Cloud project
- The OAuth consent screen is in testing mode and the requesting user is not listed as a test user
This is not a server configuration issue – it is Google’s authorization system rejecting the request at the API level.
How to diagnose which cause applies#
Step 1 – Check the response headers
The
Server
header tells you which layer generated the 403:
curl -I https://example.com/blocked-page
| Server header | The 403 came from |
|---|---|
nginx
or
openresty
| Your Nginx/OpenResty web server |
Apache
| Your Apache web server |
cloudflare
| Cloudflare CDN |
AkamaiGHost
| Akamai CDN |
| No Server header | Application-level (WordPress, Node.js, etc.) |
Step 2 – Check the error log
The web server’s error log usually explains why it returned 403:
# Nginx
tail -20 /var/log/nginx/error.log
# Apache
tail -20 /var/log/httpd/error_log
Common messages:
-
directory index of "/path/" is forbidden= directory listing disabled -
open() "/path/file" failed (13: Permission denied)= file permissions -
access forbidden by rule= Nginx deny directive -
client denied by server configuration= Apache deny rule
Step 3 – Check if it is IP-specific
Access the same URL from a different network (mobile data, VPN, different device). If it works from another IP, the block is IP-based.
Step 4 – Check if it is path-specific
Try accessing the site root versus the blocked path. If the root works but a specific path returns 403, the block is path-specific (directory permissions, .htaccess rules, WAF rules).
Step 5 – Bypass the CDN
If the site is behind a CDN, curl directly to the origin server to determine whether the 403 comes from the CDN or the origin:
curl -I https://origin-ip/blocked-page -H "Host: example.com" -k
If the origin returns 200 but the CDN returns 403, the block is at the CDN level.
Fixing a 403#
Fix file permissions
# Set standard permissions for a web directory
find /var/www/html -type d -exec chmod 755 {} \;
find /var/www/html -type f -exec chmod 644 {} \;
# Make sure the web server user owns the files
chown -R www-data:www-data /var/www/html
Adjust the username (
www-data
,
nginx
,
apache
, or your hosting account name) to match your server’s configuration. On shared hosting, the web server may run as your user account rather than a system user.
Fix .htaccess rules
If a
.htaccess
file contains
Deny from all
or
Require all denied
, either remove the rule or add an exception for your IP:
# Allow your IP while blocking everyone else
Order Deny,Allow
Deny from all
Allow from 203.0.113.50
# Apache 2.4+
Require ip 203.0.113.50
If you do not recognize the
.htaccess
rules, a WordPress security plugin may have added them. Check Wordfence, Sucuri, or iThemes Security settings. For WordPress-specific 403 issues, see the WordPress 403 troubleshooting guide.
Fix Nginx deny rules
Edit the Nginx configuration for the site and either remove the
deny all
directive or add your IP to the allow list:
location /admin/ {
allow 203.0.113.50;
allow 10.0.0.0/8;
deny all;
}
After editing, test and reload:
nginx -t && systemctl reload nginx
Fix WAF false positives
If ModSecurity or another WAF is blocking legitimate requests, identify the rule from the audit log and either:
- Disable the specific rule for the affected path
- Whitelist the specific request pattern
- Adjust the request to avoid triggering the rule
Never disable the entire WAF. Target the specific rule causing the false positive.
Fix CDN blocking
For Cloudflare:
- Go to Security > Events to find the blocked request
- Note the rule that triggered the block
- Either adjust the rule or create an exception (IP Access Rule, WAF exception)
For other CDNs, check the CDN’s security event log for the matching rule.
Fix IP-based bans
If Fail2ban banned your IP:
# Check if your IP is banned
fail2ban-client status
fail2ban-client status nginx-http-auth
# Unban your IP
fail2ban-client set nginx-http-auth unbanip 203.0.113.50
If a server-level firewall is blocking:
# Check iptables
iptables -L -n | grep 203.0.113.50
# Check firewalld
firewall-cmd --list-all
403 in APIs#
REST APIs return 403 when the authenticated user does not have the required role or permission for the requested action. This is different from a web server 403 – the application itself is enforcing access control.
Common API 403 scenarios:
- An API key is valid but lacks the required scope (read-only key trying to write)
- The authenticated user does not have the role required for the endpoint (viewer trying to delete)
- CORS preflight request is rejected (the API does not allow the requesting origin)
- Rate limiting with a 403 instead of the more standard 429 Too Many Requests
The API response body usually contains details about which permission is missing:
{
"error": "forbidden",
"message": "User does not have permission: admin.write"
}
Fix API 403 errors by checking the API documentation for required permissions and ensuring the API key or token has the correct scopes.
403 vs other errors#
| Status code | What happened |
|---|---|
| 401 Unauthorized | No credentials or invalid credentials – authenticate first |
| 403 Forbidden | Credentials accepted (or not required) but access denied |
| 404 Not Found | The resource does not exist at that URL |
| 422 Unprocessable Entity | Request is valid but the data fails validation |
| 500 Internal Server Error | The server crashed while processing the request |
Summary#
A 403 Forbidden is an authorization failure, not an authentication failure. The server understood what you asked for and refused. The fix depends on which layer generated the 403: file permissions, web server deny rules, .htaccess rules, WAF rules, CDN firewall, IP bans, or application-level access control.
Start by identifying the source from the response headers and error logs, then work through the specific fix for that layer. For WordPress-specific 403 errors, see WordPress 403 forbidden error: how to fix it.