A 403 Forbidden means Apache found the path but refused to serve it. The cause is almost always one of five things: an access rule, file permissions, a missing index file, a deny rule in .htaccess, or SELinux. This guide shows how to read the error log to see which one, then fix it without opening up more than you need.
Before you start
- A Linux VPS running Apache 2.4 on Ubuntu, Debian, AlmaLinux or Rocky Linux.
- SSH access with
sudo. - The site's document root, for example
/var/www/example.com/public.
Read the error log first
The error log names the cause. Reload the failing page, then run:
sudo tail -n 20 /var/log/apache2/error.log # Ubuntu/Debian
sudo tail -n 20 /var/log/httpd/error_log # AlmaLinux/RockyIf the site has its own ErrorLog, check that file instead. Match the message to a section below:
AH01630: client denied by server configurationpoints to a Require rule.AH00035: access to / denied... search permissions are missingpoints to directory permissions.AH01276: Cannot serve directory: No matching DirectoryIndexpoints to a missing index file.(13)Permission deniedon AlmaLinux or Rocky often points to SELinux.
Fix Require rules
Apache 2.4 uses Require to allow access. The main config denies everything at / and allows /var/www. If your site lives elsewhere, such as /srv/example.com or /home/deploy/site, you must allow it in the virtual host:
<Directory /srv/example.com/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>Old Apache 2.2 lines such as Order deny,allow and Deny from all still work only if mod_access_compat is loaded. Replace them with Require lines so there is no mix.
Test and reload:
sudo apachectl configtest
sudo systemctl reload apache2 # or httpdFix file permissions and ownership
Apache runs as www-data on Ubuntu and Debian, and apache on AlmaLinux and Rocky. It needs execute (search) permission on every directory in the path, and read permission on the files.
Check the whole path in one go:
namei -l /var/www/example.com/public/index.phpEvery directory should show an x for "others" or for a group Apache belongs to. A home directory set to drwx------ is a common blocker.
Set sensible permissions on the site:
sudo chown -R deploy:www-data /var/www/example.com
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
sudo find /var/www/example.com -type f -exec chmod 644 {} \;Replace deploy with the user who uploads files, and www-data with apache on AlmaLinux or Rocky.
Warning: Never fix a 403 with chmod -R 777. It lets any process on the server change your site, which is how many hacked sites get infected.
Add a DirectoryIndex
If you open a folder URL and there is no index.html or index.php inside, Apache returns 403 because directory listing is off. Either upload an index file, or make sure Apache knows which file to look for:
DirectoryIndex index.php index.htmlOn a PHP site, also check that PHP is actually enabled for the vhost. If PHP is missing, index.php is not on the list.
Check.htaccess deny rules
Security plugins and old configs often add rules that block whole folders or IP ranges. Look for them:
grep -RniE "deny|require|RewriteRule.*\[F" /var/www/example.com/public --include=.htaccessLines such as Require all denied, Deny from all, or a RewriteRule with the [F] flag return 403. To test, rename the file temporarily:
sudo mv /var/www/example.com/public/.htaccess /var/www/example.com/public/.htaccess.offIf the page loads, the problem is in that file. Restore it and remove only the bad rule.
Fix SELinux labels on AlmaLinux and Rocky
SELinux blocks Apache from files that do not carry a web content label, even when normal permissions are correct. This often happens after you move files from a home directory. Check the label:
ls -Z /var/www/example.com/publicFiles should show httpd_sys_content_t. To label a custom web root permanently:
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/example.com(/.*)?"
sudo restorecon -Rv /srv/example.comFor folders the app must write to, such as uploads, use httpd_sys_rw_content_t. Confirm SELinux was the cause with sudo ausearch -m avc -ts recent.
Tip: Do not disable SELinux to fix a 403. Fix the label instead, so the protection stays on.
Check it works
- Reload the page in a private browser window. It should load with status 200.
- Check from the command line:
curl -I https://example.com/should returnHTTP/1.1 200orHTTP/2 200. - Tail the error log again and confirm no new
AH01630or permission messages appear.
Troubleshooting
- 403 only on one folder, such as
/wp-admin: a deny or password rule applies to that folder. Search its.htaccessand the vhost for<Directory>or<Location>blocks. - 403 only for your IP: a firewall tool or
.htaccessblocks your address. CheckRequire not iplines and fail2ban bans. - 403 after moving the site to a home directory: the home directory is not searchable by Apache. Run
namei -land fix the directory with the missingx. - 403 behind Cloudflare with a Cloudflare-branded page: the block comes from a Cloudflare WAF rule, not Apache. Check the Cloudflare dashboard.
Next steps
- Set the right permissions for any site: Web root permissions on Linux.
- Understand SELinux denials: Fix SELinux denials.
- If the error is a 500 instead: Fix 500 Internal Server Error on Apache.
