
A 403 means Nginx received the request, found the file or path it was looking for, and deliberately refused to hand it over. That is the key difference from a 502 or 500: the server is not crashing, timing out, or failing to connect to PHP-FPM. It is working correctly and telling you no. That framing changes where you should look first — not at process managers or upstream sockets, but at permissions, index files, and explicit deny rules.
In practice, four causes account for almost every 403 you will ever see on a production Nginx site: the files or directories are owned by the wrong system user or have permission bits Nginx/PHP-FPM cannot read; there is no index.php or index.html in a directory and autoindex is off, so Nginx has nothing to serve and no listing to fall back on; a location block somewhere in the vhost config has an explicit deny all; or PHP-FPM's open_basedir directive is scoped too narrowly and blocks the app from reading a path it legitimately needs. Everything below works through those four in the order you should actually check them.
The error log tells you exactly which of the four causes you are dealing with — do not guess before checking it. Two log lines look similar but point to completely different fixes:
On a CloudStick server you rarely need to SSH in to find this. The Server panel's Web Application Logs section streams the per-site Nginx access and error log directly in the dashboard, so you can spot the exact "forbidden" reason without opening a terminal. If the log says "Permission denied," skip straight to the ownership section below. If it says "directory index ... is forbidden," go straight to the missing index section instead — permissions are fine in that case.
Ownership problems are the single most common cause of 403s, and the fix is a recursive chown back to the correct site user, not a permissions free-for-all. Nginx and PHP-FPM run as a specific system user — on a CloudStick server that is the dedicated per-site user (something like cpuser followed by a random string), not root and not your own SSH login. If files got uploaded via SFTP as root, restored from a backup with the wrong owner, or extracted from a zip that preserved someone else's UID, the web server process literally cannot read them, and Nginx returns 403 rather than crash.
Confirm the exact site username before running chown. You can find it from the site's Nginx vhost config at /etc/nginx-cs/vhosts.d/<site>.conf (look at the root and fastcgi_pass paths), or from the CloudStick dashboard under the site's overview panel. Running chown with the wrong user just moves the problem, it does not fix it.
Directories need the execute bit (755) so the web server can traverse into them; files only need to be readable (644). Resist the temptation to chmod 777 anything — that "fixes" the 403 by making the path world-writable too, which turns a permissions bug into a real security hole the moment an attacker finds an upload endpoint. If you would rather not touch a terminal at all, CloudStick's Advanced File Manager lets you fix ownership and permission bits visually from the dashboard — select the folder, apply recursive permissions, and confirm the owning user, all without an SSH session.
If the error log said "directory index ... is forbidden," permissions are not your problem — Nginx found the directory, has no index.php or index.html to serve inside it, and autoindex is off, so it has nothing left to do but refuse. This happens constantly after a deploy that fails partway through, a build step that writes output to the wrong folder, or an app scaffold that expects an index file at a path that does not exist yet.
The other common flavor is a hardening pass that added an explicit deny rule and never got cleaned up. It is normal and correct to have deny all; inside a location block that blocks access to .git, .env, or wp-config.php backups — but if that block's regex is too broad, it can accidentally swallow legitimate paths like an uploads directory or an API route. Open the site's config at /etc/nginx-cs/vhosts.d/<site>.conf and read every location block that has a deny directive; if the path being blocked overlaps with something the app actually needs to serve, narrow the regex rather than deleting the rule outright — the security intent behind it was probably correct.
When Nginx itself is fine but PHP still returns a forbidden-style error for specific requests, an overly strict open_basedir in the PHP-FPM pool config is usually the culprit. open_basedir restricts which directories a PHP script is allowed to touch, and it is a genuinely good security control — but if it is scoped to only the site's own document root, any app that needs to read a shared uploads path, a cache directory outside the webroot, or a symlinked plugin folder will get blocked, and depending on how the app surfaces the error, that can look identical to a permissions-driven 403.
Add the exact path the app needs — a shared media directory, a session storage path, a cache root — rather than widening open_basedir to something broad like the whole /home directory. After editing the pool file at /etc/php83cs/fpm-pools.d/<sitename>.conf, reload the matching PHP-FPM service so the new restriction takes effect against the socket at /run/<sitename>.sock. If you manage PHP versions and extensions through CloudStick's EasyPHP tool, this is also a good moment to confirm the site is actually running the pool you think it is — EasyPHP shows the active version per site at a glance.
Most repeat 403s trace back to deploy scripts that do not preserve ownership, or backup restores that extract files as root. Standardize your deploy and restore process to always run a chown pass back to the site user as the last step, not an afterthought you remember only after the site breaks. If you use CloudStick's Git deployment feature, the pulled files are already written as the correct site user automatically, which removes this failure mode for anything deployed that way.
As a quick recap: check the error log to know which of the four causes you are looking at, verify ownership and permissions with ls -la before touching chmod, confirm the app actually has an index file to serve, scan the vhost config for stray deny rules, and check open_basedir if PHP itself is the one refusing. If a site gets stuck in a broken state that a config fix cannot untangle, CloudStick's Website Rebuild feature gives you a clean way to reset the site's file structure and permissions from scratch rather than debugging indefinitely.

