You've set up Nginx on your VPS, pointed the domain, hit refresh, and got a plain "403 Forbidden" page. Nothing is crashing, and the server is clearly answering. It just refuses to hand over your files. This guide walks through the causes in the order we actually see them on SkyServer VPS tickets, with the commands to confirm each one.

Symptom: what a 403 from Nginx really means

A 403 means Nginx understood the request and decided not to serve it. That's different from a 404 (file not found) or a 502 (the backend is down). So the question is always: why is Nginx, or the operating system under it, saying no?

Your first stop is always the error log, because it tells you which of the causes below you're dealing with:

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

Look for one of these lines:

Log messageLikely cause
open() "/var/www/site/index.html" failed (13: Permission denied)File or parent-directory permissions, or SELinux
directory index of "/var/www/site/" is forbiddenNo index file found and autoindex is off
access forbidden by ruleAn allow/deny rule in your config
no "ssl_certificate" ... (no 403, but often confused)Different problem; check the SSL config

Cause 1: the Nginx user can't read the folder

This is the number one cause. Nginx runs as www-data on Ubuntu/Debian and nginx on AlmaLinux/Rocky. That user needs two things: read access to your files, and execute (x) access on every parent directory leading to them. Miss one folder in the chain and you get a 403.

Check the whole path in one go:

namei -l /var/www/mysite/public/index.html

Every line should allow the Nginx user at least x on directories. A common trap is a site living under /home/youruser/, where the home directory is drwx------ and blocks everyone else.

Fix it with sane defaults:

sudo chown -R www-data:www-data /var/www/mysite
sudo find /var/www/mysite -type d -exec chmod 755 {} \;
sudo find /var/www/mysite -type f -exec chmod 644 {} \;

If you must keep files in a home directory, grant traverse only, not read:

sudo chmod o+x /home/youruser

Please don't reach for chmod -R 777. It hides the symptom and opens your uploads folder to everything on the box.

Cause 2: no index file, and autoindex is off

If the log says directory index ... is forbidden, Nginx found the folder but no file matching the index directive. Check what your server block expects:

server {
    server_name example.com;
    root /var/www/mysite/public;
    index index.php index.html;
}

Then confirm the file exists at exactly that root. Typos in root (for example pointing at /var/www/mysite when the app lives in /public) are surprisingly common with Laravel and similar frameworks. Add the missing file, or correct the path. Only turn on autoindex on; if you genuinely want a public directory listing.

Cause 3: SELinux on AlmaLinux or Rocky

On RHEL-family servers, permissions can look perfect and you still get (13: Permission denied). That's SELinux. Files copied from another location keep the wrong security label. Check:

getenforce
ls -Z /var/www/mysite/public/index.html

If the type isn't httpd_sys_content_t, relabel it:

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

Don't disable SELinux just to get unblocked. Relabelling takes under a minute and keeps the protection.

Cause 4: allow/deny rules or a stray location block

Search your config for access rules you forgot about:

sudo grep -rnE "deny|allow" /etc/nginx/

A deny all; inside a broad location / block, or an IP allow-list copied from a staging setup, will 403 every visitor except the listed addresses. If you use Cloudflare in front of the server, remember Nginx sees Cloudflare's IPs unless you've configured real-IP restoration, so an allow-list of visitor IPs won't match.

Cause 5: PHP files and the wrong try_files

For PHP sites, a 403 on .php URLs usually means the PHP location block is wrong or the FastCGI socket isn't readable. Verify the socket's owner and the pool user match what Nginx uses:

ls -l /run/php/
grep -E "^(user|group|listen.owner|listen.group)" /etc/php/*/fpm/pool.d/www.conf

Then make sure you have a proper fallback so missing files go to your front controller instead of a dead end:

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

Test and reload safely

After any config change, validate before you reload. A typo here takes every site on the server down, not just the broken one:

sudo nginx -t
sudo systemctl reload nginx

Then test from the server itself, which separates server problems from browser or CDN caching:

curl -I http://127.0.0.1/ -H "Host: example.com"

Prevention

  • Deploy files as one consistent owner (for example a deploy user in the www-data group) rather than mixing root and user uploads.
  • Keep sites under /var/www instead of home directories.
  • Always run nginx -t before reloading.
  • On SELinux systems, use restorecon after copying or restoring files.
  • Clear any CDN cache after fixing, since a cached 403 can linger.

Still stuck? Open a ticket with the output of namei -l for your index file and the last 20 lines of the Nginx error log. That's usually enough for us to pinpoint it quickly.

Frequently Asked Questions

What's the difference between a 403 and a 404 in Nginx?

A 404 means the requested file or route doesn't exist. A 403 means it may well exist, but Nginx or the operating system refuses access, usually because of permissions, a missing index file, or an access rule.

Is it safe to use chmod 777 to fix a 403?

No. It makes files writable by every user on the server, which is a real security risk. Use 755 for directories and 644 for files, with the correct owner.

My permissions look right but I still get 403 on AlmaLinux. Why?

That's almost always SELinux. Check the file label with ls -Z and fix it with semanage fcontext and restorecon rather than disabling SELinux.

Why do I get 403 only on the homepage but other pages work?

The homepage request hits a directory, so Nginx looks for an index file. If none matches the index directive, and autoindex is off, you get a 403 for that URL only.

Do I need to restart Nginx after fixing permissions?

No. Permission and ownership changes take effect immediately. You only need a reload when you edit the Nginx configuration itself.