Debugging Nginx, Varnish, and PHP-FPM in CloudPanel: fixing no input file specified and operation not permitted
Back to blog

Debugging Nginx, Varnish, and PHP-FPM in CloudPanel: fixing no input file specified and operation not permitted

9/6/2026 · 4 min · Servers

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:

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:

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#

  1. No input file specified is not always a bad path: The error can occur when $document_root is incorrect, or when PHP-FPM lacks kernel/Systemd permissions to open the target file.
  2. Operation not permitted points to sandboxing: If the application user can read the file via CLI but the web service fails with EPERM, check ProtectHome in Systemd and security policies.
  3. 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 with ss -tulpn.
  4. Watch for missing semicolons in Nginx: A line missing ; causes the parser to swallow subsequent directives as parameters, leading to cryptic nginx -t errors.

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments