403 Forbidden means Nginx understood the request but refuses to serve it. Unlike a 404, the path usually exists — Nginx just isn’t allowed to (or configured to) show it. The error log almost always tells you which of these causes it is.

Step 1: read the error log

sudo tail -n 20 /var/log/nginx/error.log

Typical lines:

directory index of "/var/www/example.com/" is forbidden
open() "/var/www/example.com/index.html" failed (13: Permission denied)
access forbidden by rule, client: 198.51.100.7

Each maps to a cause below.

Cause 1: no index file

directory index of "…" is forbidden — someone requested a directory, there’s no index file, and directory listing is off (the safe default).

Fix: make sure the file exists and is listed in index:

index index.html index.htm index.php;
ls -la /var/www/example.com/

For a PHP app, index.php must be in the index directive and PHP must be configured; otherwise Nginx won’t use it.

Cause 2: the root path is wrong

The root points somewhere other than where your files are — for example /var/www/example.com while files are in /var/www/example.com/public (Laravel, Symfony) or /var/www/example.com/dist (front-end builds). Check:

sudo nginx -T | grep -n "root\|server_name"

Cause 3: file permissions

(13: Permission denied) — the Nginx worker user (www-data on Debian/Ubuntu, nginx on RHEL-family) can’t read the file. It needs:

  • read (r) on the file, and
  • execute (x) on every directory in the path, from / down.

Check the whole path at once:

namei -l /var/www/example.com/index.html
f: /var/www/example.com/index.html
drwxr-xr-x root  root  /
drwxr-xr-x root  root  var
drwxr-xr-x root  root  www
drwx------ alice alice example.com      ← others have no x: Nginx can't enter
-rw-r--r-- alice alice index.html

Fix with standard permissions:

sudo find /var/www/example.com -type d -exec chmod 755 {} +
sudo find /var/www/example.com -type f -exec chmod 644 {} +

Or give the web server group access while keeping others out:

sudo chown -R alice:www-data /var/www/example.com
sudo find /var/www/example.com -type d -exec chmod 750 {} +
sudo find /var/www/example.com -type f -exec chmod 640 {} +

See chmod and chown.

Files in a home directory

Serving from /home/alice/site often fails because home directories are 750 or 700 on modern distributions. Move the site to /var/www — better than loosening home directory permissions.

Cause 4: a deny rule

access forbidden by rule — an allow/deny directive blocked the request:

location /admin {
    allow 203.0.113.0/24;
    deny all;
}
location ~ /\. { deny all; }    # hidden files

Check whether the request matches a rule you didn’t expect, for example a hidden-file rule catching /.well-known/ (needed for Let’s Encrypt). Exclude it explicitly:

location ~ /\.(?!well-known) { deny all; }

Behind a proxy or CDN, allow rules see the proxy’s IP unless you configure the real-IP module — see real IP behind Cloudflare.

Cause 5: SELinux (AlmaLinux, Rocky, Fedora)

Permissions look perfect, but you still get Permission denied? SELinux may block Nginx from reading files without the right label — common when files were moved (rather than copied) from a home directory or placed outside /usr/share/nginx or /var/www.

ls -Z /var/www/example.com
sudo ausearch -m avc -ts recent | grep nginx

Fix the labels:

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

For directories the app writes to, use httpd_sys_rw_content_t. Don’t disable SELinux to fix this.

Cause 6: autoindex disabled on purpose

If you want a directory listing (a downloads folder, say):

location /downloads/ {
    autoindex on;
}

Only enable it where you intend to — directory listings can expose files you didn’t mean to publish.

Cause 7: PHP or the application returns 403

If the 403 page isn’t Nginx’s plain page, the application sent it: a WordPress security plugin, Laravel middleware, a WAF such as ModSecurity, or Cloudflare. Check the app’s logs or the plugin’s block log. A Cloudflare-branded 403 comes from a firewall rule or bot setting in your Cloudflare dashboard.

Quick checklist

  1. Error log message?
  2. root correct, index file present and listed?
  3. namei -l — x on every directory, r on the file for the Nginx user?
  4. Any deny rules matching?
  5. SELinux denials?
  6. Is it the app, a WAF or a CDN rather than Nginx?

After fixing, sudo nginx -t && sudo systemctl reload nginx and test with curl -I https://example.com/. Related: Nginx 502 Bad Gateway and Nginx server blocks.

  • Nginx
  • Troubleshooting
  • Permissions