Nginx refused to serve — and it's telling you why in error.log: file permissions, missing index directive, or a deny rule. Four layers to check, in order.
Every component of the path needs +x for the nginx user, the file needs +r. namei -l /var/www/site/index.html walks the whole chain showing exactly which link fails.
Requesting / when index.html isn't present and autoindex is off → 403 (not 404, by design). Fix: index index.html; or create the file.
deny directives, geo rules, or SELinux denials (CentOS/RHEL: audit.log shows avc denied). Test by temporarily checking error.log wording: 'client denied' = nginx rule; 'Permission denied' = filesystem/SELinux.
sudo tail -5 /var/log/nginx/error.log # 'client denied' = config rule; 'Permission denied' = filesystem/SELinux
namei -l /var/www/site/index.html # every component needs x for www-data, file needs r
sudo chown -R www-data:www-data /var/www/site && sudo find /var/www/site -type d -exec chmod 755 {} \; && find /var/www/site -type f -exec chmod 644 {} \;
# location / { index index.html; } — or autoindex on; only where listing is intended
chmod -R 777 'fixes' it while creating a far worse problem: any process can rewrite your webroot. It also hides the real missing-permission link. SELinux contexts (restorecon -Rv) trip RHEL-family servers where everything looks permission-correct.
When the directory exists but no index file matches (and listing is off), nginx refuses rather than reveals the directory structure. It's by design — create the index file or set the index directive.
The error log wording decides: 'client denied by server configuration' = a rule you wrote; 'Permission denied' / open() errors = filesystem or SELinux. Same 403, different owners.
Our most-documented failures, packaged as ready-to-ship starter kits: Docker, Kubernetes, and Terraform.
Browse the template store →One-time. Yours to modify. Instant download from the NinjaOps template store.