
A 403 Forbidden error means the server understood your request and deliberately refused it: the page exists, but the server, its firewall or the application decided you may not have it. Signing in again rarely helps. The fix depends on who refused — the file system, nginx or Apache, a WAF like Cloudflare, or the app itself.
Unlike 401 (not authenticated), 403 says: "I know what you asked for, and the answer is no." Unlike 404, the resource is usually there. Each layer that can say no leaves a different trace — in the response headers, in the error page and in the server logs — and this guide shows how to read them, first as a visitor, then as the site owner.
What 403 Means
Per RFC 9110 §15.5.4, 403 indicates the server understands the request but refuses to authorize it. Re-authentication will not help — the decision is server-side (ACL, permissions, policy). The RFC also allows a server that wants to hide a resource's existence to answer 404 instead, which is why some sites return 404 for paths you are simply not allowed to see.
| Code | What the server is saying | Does retrying or signing in help? |
|---|---|---|
| 401 Unauthorized | "I don't know who you are" — credentials missing or invalid | Yes: send valid credentials |
| 403 Forbidden | "I know the request, and you may not have it" | Usually no: permissions, rules or policy must change |
| 404 Not Found | "Nothing here" (or "I won't admit it exists") | No: fix the URL or restore the resource |
| 429 Too Many Requests | "Slow down" — a rate limit was hit | Yes, after waiting (see Retry-After) |
Who refused: reading the 403 page and its headers
Most 403 pages are not caused by anything on your computer. The top search results for this error tell visitors to clear their cache — that fixes the rare case of a stale cookie and nothing else, because the refusal is made on the server. The quickest way to find the responsible layer is the error page and the headers:
| Who refused | Telltale sign | Who can fix it |
|---|---|---|
| Cloudflare WAF / firewall rule | Branded page with "Ray ID"; headers server: cloudflare, cf-ray; error code 1020 on the page | Site owner (Security → WAF events) |
| nginx | Plain "403 Forbidden" page with nginx in the footer; Server: nginx | Site owner: permissions, deny, missing index |
| Apache | "Forbidden — You don't have permission to access this resource." | Site owner: Require, .htaccess, permissions |
| ModSecurity or a hosting WAF | Generic 403 right after submitting a form or a URL with odd characters | Site owner: tune or exclude the rule |
| Application (WordPress plugin, API) | Site-styled page or JSON like {"error":"forbidden"} | Site owner, or you if a token/role is wrong |
| Object storage (Amazon S3) | XML body with <Code>AccessDenied</Code> | Bucket owner: policy, ACL, or the key does not exist |
| Your network (corporate proxy, antivirus web shield) | Page branded by your company's proxy or security suite, same error on many unrelated sites | Your IT team |
Is a 403 your fault, then? Only when your account lacks the role, your API token is wrong or expired, a cookie is corrupted, or something on your side — a VPN, a privacy relay, an extension, a corporate filter — makes your requests look suspicious.
Does 403 Forbidden mean I'm blocked?
Sometimes. A 403 can mean your IP address, country, browser signature or behavior tripped a rule — or simply that the URL is not meant to be public (an admin area, a directory without an index page, a private file). If the same 403 appears on one site only, from every device you own, but the site works from a phone on mobile data, your IP or network is the likely trigger. If every device and network gets it, the site is misconfigured and only the owner can fix it. Report it with the Ray ID or the exact URL and time — that is what the owner needs to find the log entry.
How do I fix a 403 Forbidden error as a visitor?
- Check the URL. A URL ending in a folder (
/files/) often returns 403 because directory listing is disabled. Remove the trailing part or go back to the home page. - Reload once and sign in. If the page belongs to an account area, sign out and back in; a session that lost its role returns 403.
- Delete cookies for that one site — not your whole browser. A corrupted or oversized cookie is a real, if uncommon, cause.
- Turn off extensions and privacy relays temporarily. VPNs, iCloud Private Relay, ad-blocking DNS and "anti-tracking" extensions share IPs or strip headers, and WAFs often score those as bots. Turning them off is a diagnostic step, not a workaround.
- Try another network. Home Wi-Fi vs mobile data tells you whether the refusal is tied to your IP.
- Contact the site. Include the time, the URL and any Ray ID or reference number on the page.
How to fix 403 Forbidden on Google Chrome (Windows 11, macOS)
- Open the page in an Incognito window (
Ctrl+Shift+N, on macOSCmd+Shift+N). Incognito runs without your cookies and, by default, without extensions — if the page loads there, the cause is in your profile. - Delete that site's data: click the icon left of the address bar → Site settings → Delete data, or open
chrome://settings/content/alland search for the domain. - Disable extensions one by one at
chrome://extensions. - On Windows 11, if Edge and Chrome both show a 403 page branded by your antivirus or your employer's proxy, the block is on your side of the network — the website never saw the request.
403 Forbidden on Android and Chrome for Android
- Tap the icon left of the address → Cookies and site data (or Site settings) → delete the data for that site.
- Switch between Wi-Fi and mobile data to see whether the refusal follows your IP.
- If you use a private DNS or a VPN app, turn it off and retry.
- In an app (not a browser), a 403 usually means your session or token expired — sign out of the app and sign in again, or update it.
403 Forbidden on iPhone (Safari)
- Remove the site's data: Settings → Safari → Advanced → Website Data (on iOS 18: Settings → Apps → Safari → Advanced → Website Data), search for the domain and delete it.
- If iCloud Private Relay is on, some sites refuse its shared exit addresses. Turn it off temporarily under Settings → your name → iCloud → Private Relay and retry.
- Test the same URL on mobile data.
403 Forbidden in Postman and API clients
Postman has no browser cache, so a 403 there is always about the request itself. Check, in order:
- Authorization. The token is present but lacks the scope or role for this endpoint. Many APIs return 401 for a missing token and 403 for a valid token with insufficient rights.
- The right header.
Authorization: Bearer …vsX-API-Key— sending the key in the wrong header looks like "no rights" to some APIs. - CSRF. Session-cookie APIs (Django, Laravel, Rails) reject state-changing requests without a CSRF token with 403.
- IP allowlists and WAFs. Your office IP may be allowed while your laptop at home is not.
- Cloud specifics. Amazon S3 returns 403
AccessDeniedfor a key that does not exist when the caller lackss3:ListBucket; Amazon API Gateway returns 403Missing Authentication Tokenfor a wrong path or method, not only for missing auth.
CORS is not the cause of a 403 in Postman — CORS is enforced by browsers only. If the request works in Postman but fails in the browser, compare the headers the browser sends, including Origin.
8 Common Causes on the Server
- Wrong file permissions — 600, 640 instead of 644, or a parent directory without the execute bit.
- File owner is not the web server user (root:root instead of www-data:www-data) combined with restrictive permissions.
- No index file in the directory and directory listing disabled.
- Block in .htaccess or nginx (
deny all,Require all denied). - ModSecurity or WAF rule blocked a suspicious request.
- IP, country or bot rule at Cloudflare, another CDN or the web server.
- SELinux/AppArmor denies web server access to the file.
- Hotlink protection — Referer does not match the allowlist.
A firewall that drops packets (iptables, nftables, fail2ban with its default firewall action) does not produce a 403 at all — the client sees a timeout or "connection refused". A 403 is always written by something that speaks HTTP.
Diagnosis
Always start with logs. 403 is a server code, and the reason is there. If you are unsure where your logs live, see where nginx logs live and how to read them.
# nginx
tail -50 /var/log/nginx/error.log | grep -i "forbidden\|403"
# Apache
tail -50 /var/log/apache2/error.log
tail -50 /var/log/apache2/access.log | grep " 403 "
# ModSecurity
tail -50 /var/log/modsec_audit.log
# Check permissions
ls -la /var/www/html/problem-file
# Expected: -rw-r--r-- 1 www-data www-data
# Check every directory on the path (the web server needs +x on each)
namei -l /var/www/html/problem-file
The exact log line tells you which fix applies:
| Log line | Meaning | Fix |
|---|---|---|
directory index of "/var/www/html/dir/" is forbidden (nginx) | Directory requested, no index file, autoindex off | Add an index file or correct the index directive |
open() "…" failed (13: Permission denied) (nginx) | The worker user cannot read the file or traverse a parent directory | Permissions/ownership; namei -l; SELinux context |
access forbidden by rule (nginx) | A deny directive matched | Review allow/deny in the matching location |
AH01630: client denied by server configuration (Apache) | A Require rule refused the client | Fix Require in the vhost or .htaccess |
AH01276: Cannot serve directory … (Apache) | No matching DirectoryIndex, listing disabled by Options | Add the index file or fix DirectoryIndex |
AH00035: access to … denied … search permissions are missing (Apache) | A parent directory lacks the execute bit | chmod 755 on each directory in the path |
Nothing in your logs while the visitor clearly got a 403? Then the refusal happened in front of your server — at a CDN, a hosting WAF or the visitor's own network.
How to check a 403 from the outside
First see what a real client receives. From a terminal:
# Status line and headers only
curl -sI https://example.com/path
# Same request with a browser-like User-Agent: if this one gets 200,
# a rule is filtering by User-Agent
curl -sI -A "Mozilla/5.0" https://example.com/path
Look for server: cloudflare with cf-ray (Cloudflare answered), cf-mitigated: challenge (a challenge, not a hard block), x-amz-request-id (S3), or your own Server header (the origin answered). Without a terminal, use the HTTP Header Checker for the external response and Security Scanner to check whether the server leaks information through headers. If a URL goes through several hops before the 403, the redirect checker shows which hop refuses.
8 Solutions
1. Fix file permissions
# Files
find /var/www/html -type f -exec chmod 644 {} \;
# Directories
find /var/www/html -type d -exec chmod 755 {} \;
# Owner
chown -R www-data:www-data /var/www/html
The web server user differs by distribution: www-data on Debian and Ubuntu, nginx or apache on RHEL-family systems. Check it with ps -o user= -C nginx or the user directive in nginx.conf. Writable-by-the-server ownership is not required to serve files; read access is.
2. Verify index file
# nginx
index index.php index.html;
# Apache
DirectoryIndex index.php index.html
In nginx, try_files $uri $uri/ =404; sends a directory request to the index lookup — a directory with no index then returns 403, not 404. That is the most common "403 on the home page" after a fresh deploy that put files one level too deep.
3. Find blocks in .htaccess
# Look for:
Deny from all
Require all denied
# Replace with:
Require all granted
On Apache 2.4, old Order/Allow/Deny lines work only through mod_access_compat; mixing them with Require in the same scope produces surprising results. Use one style. More on syntax in the .htaccess guide.
4. 403 Forbidden in nginx: deny directives
# Problematic:
location ~ \.(env|git|htaccess)$ {
deny all;
}
# If too broad — review the pattern
Remember that nginx uses one location: a regex location like the one above wins over a prefix location unless the prefix uses ^~. allow and deny are checked in order, and the first match decides:
location /admin/ {
allow 203.0.113.10;
deny all;
}
After any change, test and reload: nginx -t && systemctl reload nginx.
5. ModSecurity false positive
# Identify the rule from the log:
# [id "949110"] [msg "Inbound Anomaly Score Exceeded"]
# Disable for a specific URL
SecRule REQUEST_URI "@beginsWith /api/safe-endpoint" \
"id:1001,phase:1,pass,nolog,ctl:ruleRemoveById=949110"
Rule 949110 is the OWASP Core Rule Set's blocking decision, not the detection itself; removing it switches off blocking for that URL entirely. Better practice is to find the individual rule IDs listed above it in the audit log (for example a 942xxx SQL-injection rule matching a legitimate field) and remove only those. Avoid the allow action here: it stops rule processing for the request altogether.
6. Check IP blocks
Firewall-level bans cause timeouts rather than 403, but rule them out when "the site is down for one person":
# fail2ban
fail2ban-client status
fail2ban-client set nginx-http-auth unbanip YOUR_IP
# iptables
iptables -L -n | grep DROP
iptables -D INPUT -s BLOCKED_IP -j DROP
Replace nginx-http-auth with the jail that banned the address (the output of fail2ban-client status lists them). For jail setup, see fail2ban setup. For a real 403 by IP, look at HTTP-level rules: nginx deny, Apache Require not ip, Cloudflare IP Access rules and custom WAF rules. A Cloudflare block shows error 1020 — see how to find and fix the rule behind 1020.
7. SELinux context (CentOS/RHEL)
ls -Z /var/www/html/
# Should be httpd_sys_content_t
restorecon -Rv /var/www/html/
chcon -t httpd_sys_content_t /var/www/html/newfile.php
Confirm SELinux is the cause before changing anything: getenforce shows the mode, and ausearch -m avc -ts recent lists the denials. chcon does not survive a relabel; for content outside /var/www, make the rule persistent:
semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"
restorecon -Rv /srv/www
8. WordPress
- Rename
.htaccess, visit Settings → Permalinks to regenerate. - Review security plugins (Wordfence, iThemes) — they may have blocked your IP.
chmod 644 wp-config.php,chmod 755 wp-content/- If a 403 appears only on
admin-ajax.phpor the REST API (/wp-json/), a security plugin or host WAF is filtering it — check its log before disabling anything.
Hotlink protection that blocks your own pages
location ~* \.(jpg|jpeg|png|webp|gif)$ {
valid_referers none blocked server_names *.example.com;
if ($invalid_referer) {
return 403;
}
}
Keep none and blocked in the list: browsers and privacy tools often send no Referer, and without those values legitimate visitors get 403 on images.
403 on /admin or /wp-admin
If the site works but admin returns 403:
- Check IP allowlist in
.htaccessor nginx. - Review Cloudflare Firewall Rules on the admin path.
- Check fail2ban — your IP may be banned after failed logins.
- Verify 2FA — some plugins return 403 on wrong TOTP.
FAQ
Is 403 Forbidden my fault?
Rarely. Most 403s are server-side decisions. It is on your side only when your account lacks rights, a token or cookie is wrong, or a VPN, relay, extension or corporate filter makes your traffic look suspicious.
Is a 403 error a permanent ban?
Usually not. Automated blocks expire (fail2ban's default ban time is 10 minutes; WAF rate rules reset), while manual IP or account blocks last until someone removes them. A 403 caused by permissions or configuration stays until the owner fixes it.
What is the difference between 401 and 403?
401 = "authenticate" (no credentials). 403 = "you have no access" (credentials exist but insufficient rights). For the 401 side, see HTTP 401 Unauthorized: causes and fixes.
After scp the file returns 403, why?
The uploaded file keeps restrictive permissions or the wrong owner. Run chown www-data:www-data file && chmod 644 file, and check the parent directories with namei -l.
How do I tell 403 from the server vs from a WAF?
Check the Server: and cf-ray headers. Cloudflare usually shows its own branded page with an ID. The Enterno.io HTTP Checker shows all headers. No matching line in your origin's logs also points to a layer in front of it.
403 for Googlebot — is that bad?
Critical. Google cannot index the page. Review robots.txt and WAF — make sure Googlebot (official IP list) is not blocked. robots.txt disallows produce "blocked by robots.txt", not 403, so a 403 for Googlebot points to a firewall, bot-management or User-Agent rule.
Conclusion
403 is a conversation about access rights. Start with logs, verify file permissions and ownership, hunt for deny rules in .htaccess and nginx, rule out WAF and IP blocks. Configure monitoring with expected_code=200 to catch 403 regressions instantly.