403 Forbidden means the server found what you asked for but will not show it. On your own server the cause is almost always one of four things: file permissions, a missing index file, a rule that denies access, or (on AlmaLinux, Rocky and RHEL) an SELinux label. The error log says which.
If you are visiting the site
Check the address. A 403 often appears when a link points at a folder rather than a page, so try the site’s home page. If you were logged in, log in again. Some sites also block certain countries, VPNs or networks; if you use a VPN, switch it off and reload. If the whole site gives 403, only the owner can fix it.
Read the log first
Load the page, then:
sudo tail -n 20 /var/log/nginx/error.log
sudo tail -n 20 /var/log/apache2/error.log
| Log says | Cause | Go to |
|---|---|---|
open() "/var/www/..." failed (13: Permission denied) | The web server cannot read the file or a folder above it | Step 1, then step 4 |
directory index of "/var/www/example.com/public/" is forbidden | No index file and directory listing is off | Step 2 |
access forbidden by rule | A deny rule in Nginx matched | Step 3 |
AH01630: client denied by server configuration | An Apache Require rule denied it | Step 3 |
AH01797: client denied by server configuration in .htaccess | An .htaccess rule denied it | Step 3 |
| Nothing at all | The 403 came from a CDN, a security plugin or a firewall in front | Step 5 |
If you run the site
1. File permissions and ownership
The web serverWeb server The program that answers visitors’ browsers and sends them your website. Also used for the computer it runs on. More about Web server → needs to read the file and be allowed to pass through every folder above it. Check the whole path in one go:
namei -l /var/www/example.com/public/index.php
Each line shows a folder or file with its permissions and owner. Every folder needs the execute (x) permission for the web server’s user, and the file needs read (r). A common trap is a site in a home folder: /home/alice is often 700, which blocks the web server even when the site’s own files are fine.
The usual safe settings are folders 755 and files 644, owned by the user that PHP runs as:
sudo chown -R www-data:www-data /var/www/example.com/public
sudo find /var/www/example.com/public -type d -exec chmod 755 {} \;
sudo find /var/www/example.com/public -type f -exec chmod 644 {} \;
Use nginx or apache instead of www-data on RHEL-based systems, or the site’s own user if PHP-FPMPHP-FPM The part of the server that runs PHP code, the language WordPress and many sites are written in, and hands the finished page to the web server. More about PHP-FPM → runs as one. Avoid 777: it does not fix a 403 that has another cause, and it lets any process on the server change your files.
2. Missing index file
When a visitor asks for a folder, such as the home page, the server looks for an index file. If none exists and directory listing is off, the answer is 403. Check the file is there, and that the server knows its name:
ls -la /var/www/example.com/public/
grep -R "index " /etc/nginx/sites-enabled/ /etc/nginx/conf.d/
For a PHP site, NginxNginx A fast, popular program that sends web pages to visitors and can pass requests on to apps behind it. More about Nginx → needs index index.php index.html;. On ApacheApache One of the most common programs that sends web pages to visitors. It is the waiter between your website’s files and the people asking for them. More about Apache → the equivalent is DirectoryIndex index.php index.html. An upload that went into the wrong folder, such as public/public/, causes this too.
3. Server rules that deny access
Look for rules that block the path or the visitor:
grep -RnE "deny|allow" /etc/nginx/sites-enabled/ /etc/nginx/conf.d/
grep -RnE "Require|Deny|Order" /etc/apache2/sites-enabled/ /var/www/example.com/public/.htaccess
Common culprits:
- A rule meant for one folder, such as
/wp-admin/limited to your office IP, that now catches more than intended. location ~ /\.rules that block hidden files, which also block/.well-known/and break Let’s Encrypt renewals unless excepted.- An old Apache 2.2
Order deny,allowblock on Apache 2.4, where it behaves differently. Replace it withRequire all granted. - An
.htaccessfile copied from another site with aDeny from allor an IP list.
After editing, test and reload: sudo nginx -t && sudo systemctl reload nginx, or sudo apachectl configtest && sudo systemctl reload apache2.
4. SELinux labels on RHEL-based systems
On AlmaLinux, Rocky and RHEL, files must carry the right SELinux label as well as normal permissions. Files moved with mv from a home folder keep the wrong label and get a 403, even with perfect permissions. Check and fix:
ls -Z /var/www/example.com/public/index.php
sudo restorecon -Rv /var/www/example.com
The label should include httpd_sys_content_t. If your site lives outside /var/www, tell SELinux about the new path first:
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/example(/.*)?"
sudo restorecon -Rv /srv/example
Folders WordPress writes to, such as wp-content/uploads, need httpd_sys_rw_content_t instead.
5. Security plugins, firewalls and CDNs
If the server’s log shows nothing, the request never reached your site’s code. Check, in order:
- A CDNCDN A network of servers around the world that keep copies of your site’s files, so each visitor gets them from somewhere nearby. Faster pages, less work for your server. More about CDN → or proxy. A Cloudflare-branded 403 comes from a firewallFirewall A gatekeeper that decides which connections are allowed into a server and blocks the rest. More about Firewall → rule, a security level setting or bot protection. The Cloudflare dashboard’s Security > Events page shows which rule blocked the request.
- A web application firewall such as ModSecurity. Its log, often
/var/log/modsec_audit.logor/var/log/apache2/modsec_audit.log, names the rule ID that matched. - WordPress security plugins. Wordfence, iThemes and similar plugins block IPs, countries and “suspicious” requests. Check the plugin’s blocking log, or rename its folder to rule it out.
- Hotlink protection. A 403 only on images loaded from another site is usually deliberate hotlink protection.
Confirm it is fixed
curl -I https://example.com/the-page/
HTTP/2 200 means it works. Test as a logged-out visitor in a private window too, since a rule may only affect visitors who are not logged in.
Related
- 404 Not Found, when the server cannot find the page at all.
- How to set up a firewall on Linux.
Something out of date? Software changes. If a step no longer works, tell us and we will check it and update the page.



