Not every 404 or 500 error in web hosting comes from application code bugs. I recently dealt with a multi-tier troubleshooting scenario involving Nginx edge SSL termination, Varnish caching in the middle, an internal Nginx backend on port 8080, and PHP-FPM serving a Laravel application.
The initial symptom was the familiar message:
No input file specified.
The investigation spanned from Nginx VirtualHost blocks all the way down to Systemd filesystem isolation on Linux. Here is the step-by-step breakdown of how I isolated the root cause, eliminated false leads, and established a working configuration.
1. Environment architecture and initial symptoms#
The server ran CloudPanel with the following port layout:
- Ports 80 & 443 (Nginx Edge): Handles SSL/HTTP2 termination, proxying dynamic traffic to Varnish (
{{varnish_proxy_pass}}) or serving static assets directly from disk. - Port 8080 (Nginx Backend): Receives internal requests and connects via FastCGI to the PHP-FPM pool.
- Application: Laravel, where all dynamic requests must route through
/public/index.php. - Domains: Two subdomains pointing to the same application (
dev1.domain.com.brfor frontend anddev2.domain.com.brfor dashboard).
Requesting static files directly, such as https://dev1.domain.com.br/robots.txt, returned HTTP 200 immediately. This confirmed that Nginx had filesystem read access, but FastCGI execution was failing when handing requests over to PHP-FPM.
2. Isolating the issue step by step#
Document root verification#
The first step was inspecting the physical path of index.php:
cd /home/user/htdocs/domain.com.br/public_html/public
ls -la
I corrected a nested folder issue from a recent deployment (/public/public/) so the Nginx root pointed cleanly to /home/user/htdocs/domain.com.br/public_html/public. The error persisted.
Testing with a standalone PHP script#
To rule out Laravel routing, middleware, or .env configuration issues, I created a minimal test script:
echo "<?php phpinfo();" > /home/user/htdocs/domain.com.br/public_html/public/test.php
chown user:user /home/user/htdocs/domain.com.br/public_html/public/test.php
Visiting https://dev1.domain.com.br/test.php still returned "No input file specified". This confirmed the breakdown was in the FastCGI communication between Nginx and PHP-FPM.
Discovering the actual PHP-FPM pool port#
Checking journalctl -u php8.4-fpm revealed that PHP 8.4 was listening on internal dynamic TCP ports (127.0.0.1:19000, 19001, 19002, 19003).
Checking the domain pool config in /etc/php/8.4/fpm/pool.d/domain.com.br.conf:
[domain.com.br]
listen = 127.0.0.1:19003
user = user
group = user
pm = ondemand
Running ss -tulpn | grep 19003 confirmed that this specific pool ran on port 19003. Nginx had been configured with a generic placeholder {{php_fpm_port}}, which pointed to port 19000 (the default clp user pool), causing cross-user permission failures.
I updated fastcgi_pass on the 8080 block to explicitly target 127.0.0.1:19003.
3. The turning point: operation not permitted error#
Monitoring /var/log/nginx/error.log while loading test.php revealed the critical upstream error:
FastCGI sent in stderr: "Unable to open primary script: /home/user/htdocs/domain.com.br/public_html/public/test.php (Operation not permitted)" while reading response header from upstream, client: 172.x.x.x, server: dev1.domain.com.br, request: "GET /test.php HTTP/1.0", upstream: "fastcgi://127.0.0.1:19003"
On Linux, there is a distinct difference between these two errors:
Permission denied(EACCES): Traditional file permission failure (chmod or chown).Operation not permitted(EPERM): Kernel security policy, mount restrictions, or Systemd sandboxing blocking theopen()syscall.
I ran a sanity check using the application user:
sudo -u user cat /home/user/htdocs/domain.com.br/public_html/public/test.php
The command succeeded. The user had physical file access, but the PHP-FPM worker process was constrained inside a Systemd sandbox.
Checking the directory tree and adjusting systemd#
I inspected directory traversal permissions with namei:
namei -om /home/user/htdocs/domain.com.br/public_html/public/test.php
The /home directory had restrictive 701 permissions (drwx-----x). Combined with default Debian 12 Systemd service protections, PHP-FPM was prevented from traversing /home.
To disable this restriction in Systemd, I created a drop-in override:
systemctl edit php8.4-fpm
Adding:
[Service]
ProtectHome=false
And applying the changes:
systemctl daemon-reload
systemctl restart php8.4-fpm
4. Fixing Nginx syntax error: invalid number of arguments in root#
When running nginx -t, the validator threw:
nginx: [warn] the "listen ... http2" directive is deprecated, use the "http2" directive instead
nginx: [emerg] invalid number of arguments in "root" directive in /etc/nginx/sites-enabled/domain.com.br.conf:89
The cause was a missing semicolon on the root line in the port 8080 block:
# Incorrect:
root /home/user/htdocs/domain.com.br/public_html/public
try_files $uri $uri/ /index.php?$args;
# Correct:
root /home/user/htdocs/domain.com.br/public_html/public;
try_files $uri $uri/ /index.php?$args;
Without the ;, Nginx treated try_files as a second parameter to root. I also updated the HTTP/2 directive to the modern format (http2 on;).
5. Consolidated working virtualhost configuration#
Here is the complete working Nginx configuration handling secondary domain redirects, passing requests to Varnish, and routing FastCGI directly to the domain pool on port 19003:
# BLOCK 1: Secondary and Base Domain Redirects
server {
listen 80;
listen [::]:80;
listen 443 quic;
listen 443 ssl;
listen [::]:443 quic;
listen [::]:443 ssl;
http2 on;
{{ssl_certificate_key}}
{{ssl_certificate}}
server_name www.domain.com.br domain.com.br dev2.domain.com.br;
return 301 https://dev1.domain.com.br$request_uri;
}
# BLOCK 2: Reverse Proxy, SSL Termination, and Static Assets
server {
listen 80;
listen [::]:80;
listen 443 quic;
listen 443 ssl;
listen [::]:443 quic;
listen [::]:443 ssl;
http2 on;
http3 off;
{{ssl_certificate_key}}
{{ssl_certificate}}
server_name dev1.domain.com.br;
root /home/user/htdocs/domain.com.br/public_html/public;
{{nginx_access_log}}
{{nginx_error_log}}
if ($scheme != "https") {
return 301 https://$host$request_uri;
}
location ~ /.well-known {
auth_basic off;
allow all;
}
{{settings}}
include /etc/nginx/global_settings;
location / {
{{varnish_proxy_pass}}
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_hide_header X-Varnish;
proxy_redirect off;
proxy_max_temp_file_size 0;
proxy_connect_timeout 720;
proxy_send_timeout 720;
proxy_read_timeout 720;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
proxy_temp_file_write_size 256k;
}
location ~* ^.+\.(css|js|jpg|jpeg|gif|png|ico|gz|svg|svgz|ttf|otf|woff|woff2|eot|mp4|ogg|ogv|webm|webp|zip|swf|map)$ {
add_header Access-Control-Allow-Origin "*";
add_header alt-svc 'h3=":443"; ma=86400';
expires max;
access_log off;
}
}
# BLOCK 3: Internal Backend (Nginx to FastCGI PHP-FPM)
server {
listen 8080;
listen [::]:8080;
server_name dev1.domain.com.br;
root /home/user/htdocs/domain.com.br/public_html/public;
try_files $uri $uri/ /index.php?$args;
index index.php index.html;
location ~ \.php$ {
include fastcgi_params;
fastcgi_intercept_errors on;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
try_files $uri =404;
fastcgi_read_timeout 3600;
fastcgi_send_timeout 3600;
fastcgi_param HTTPS "on";
fastcgi_param SERVER_PORT 443;
fastcgi_pass 127.0.0.1:19003;
fastcgi_param PHP_VALUE "{{php_settings}}";
}
location ~* ^.+\.(css|js|jpg|jpeg|gif|png|ico|gz|svg|svgz|ttf|otf|woff|woff2|eot|mp4|ogg|ogv|webm|webp|zip|swf|map)$ {
add_header Access-Control-Allow-Origin "*";
expires max;
access_log off;
}
}
Key takeaways from this investigation#
No input file specifiedis not always a bad path: The error can occur when$document_rootis incorrect, or when PHP-FPM lacks kernel/Systemd permissions to open the target file.Operation not permittedpoints to sandboxing: If the application user can read the file via CLI but the web service fails withEPERM, checkProtectHomein Systemd and security policies.- Verify the active pool port: In environments managing multiple PHP versions and pools, never assume the default port. Inspect
/etc/php/X.Y/fpm/pool.d/and verify withss -tulpn. - Watch for missing semicolons in Nginx: A line missing
;causes the parser to swallow subsequent directives as parameters, leading to crypticnginx -terrors.
Was this article helpful?
Leave a quick reaction to help prioritize future technical guides:
This post is licensed under CC BY-NC.



Comments
Join the discussion below.
0 comments