Troubleshooting

How to fix 403 Forbidden errors in Apache

Fix Apache 403 Forbidden errors by checking Require rules, file permissions, a missing index file,.htaccess deny rules and SELinux labels, one cause at a time.

BeginnerLinux server5 min read

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:

Bash
sudo tail -n 20 /var/log/apache2/error.log   # Ubuntu/Debian
sudo tail -n 20 /var/log/httpd/error_log     # AlmaLinux/Rocky

If the site has its own ErrorLog, check that file instead. Match the message to a section below:

  • AH01630: client denied by server configuration points to a Require rule.
  • AH00035: access to / denied... search permissions are missing points to directory permissions.
  • AH01276: Cannot serve directory: No matching DirectoryIndex points to a missing index file.
  • (13)Permission denied on 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:

Apache
<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:

Bash
sudo apachectl configtest
sudo systemctl reload apache2   # or httpd

Fix 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:

Bash
namei -l /var/www/example.com/public/index.php

Every 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:

Bash
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:

Apache
DirectoryIndex index.php index.html

On 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:

Bash
grep -RniE "deny|require|RewriteRule.*\[F" /var/www/example.com/public --include=.htaccess

Lines such as Require all denied, Deny from all, or a RewriteRule with the [F] flag return 403. To test, rename the file temporarily:

Bash
sudo mv /var/www/example.com/public/.htaccess /var/www/example.com/public/.htaccess.off

If 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:

Bash
ls -Z /var/www/example.com/public

Files should show httpd_sys_content_t. To label a custom web root permanently:

Bash
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/example.com(/.*)?"
sudo restorecon -Rv /srv/example.com

For 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

  1. Reload the page in a private browser window. It should load with status 200.
  2. Check from the command line: curl -I https://example.com/ should return HTTP/1.1 200 or HTTP/2 200.
  3. Tail the error log again and confirm no new AH01630 or permission messages appear.

Troubleshooting

  • 403 only on one folder, such as /wp-admin: a deny or password rule applies to that folder. Search its .htaccess and the vhost for <Directory> or <Location> blocks.
  • 403 only for your IP: a firewall tool or .htaccess blocks your address. Check Require not ip lines and fail2ban bans.
  • 403 after moving the site to a home directory: the home directory is not searchable by Apache. Run namei -l and fix the directory with the missing x.
  • 403 behind Cloudflare with a Cloudflare-branded page: the block comes from a Cloudflare WAF rule, not Apache. Check the Cloudflare dashboard.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.