• We value your experience with Plesk during 2024
    Plesk strives to perform even better in 2025. To help us improve further, please answer a few questions about your experience with Plesk Obsidian 2024.
    Please take this short survey:

    https://pt-research.typeform.com/to/AmZvSXkx
  • The Horde webmail has been deprecated. Its complete removal is scheduled for April 2025. For details and recommended actions, see the Feature and Deprecation Plan.
  • We’re working on enhancing the Monitoring feature in Plesk, and we could really use your expertise! If you’re open to sharing your experiences with server and website monitoring or providing feedback, we’d love to have a one-hour online meeting with you.

Question How to set up chroot for php-fpm?

frame86

New Pleskian
Server operating system version
Ubuntu 18.04.4 LTS
Plesk version and microupdate number
18.0.50
I want to set up chroot environment not only for shell access. So how to do this manually for PHP too?

First part is simple - just add the configuration like
[php-fpm-pool-settings]
chroot = /var/www/vhosts/your-domain.tld

and restart php-fpm.

But I stuck to configure nginx to supply the correct headers for PHP.
Calling a script in the browser ends with: File not found or Primary script unknown

Assuming docroot is /httpdocs inside the chroot-directory I tried
fastcgi_param SCRIPT_FILENAME /httpdocs$fastcgi_script_name;
in the nginx directive for php but it still doesn't work and the proxy_error_log just shows a similar error message.

Anyone got this working?
 
PHP-FPM in a subscription already runs under the subscription user, not as root. Unless you are using Cloud Linux with CageFS it is impossible to guarantee that a PHP function cannot access parts of the server beyond the subscription. The closest you can get is to disable execution permissions of your /tmp partition on the operating system, disable all shell access commands in PHP and to have the open_basedir limit in effect for your subscription. If malicious scripts manage to circumvent open_basedir they will still run as the subscription user, so they cannot escalate to root level.
 
Last edited:
I'm talking about chroot not root access or preventing user to run as root.

I want chroot to prevent file access because

open_basedir is a huge bottleneck as realpath cache is disabled too when this option is enabled.

The webroot needs some shell commands, I know how to provide them, that was not question.

This feature is not uncommon, I just don't know how to set the paths right so the script can be found by PHP.
 
The problem with $_SERVER variables in chrooted PHP-FPM is something that The PHP Group never tackled, probably, because the demand is low: PHP :: Bug #55322 :: Apache : TRANSLATED_PATH doesn't consider chroot and PHP :: Bug #62279 :: PHP-FPM chroot never-solved problems (extends #55322) are not solved.

It also leaves a lot of questions on the table, i.e.: should the fd to session files leak outside of the chroot, or should it be contained? what about ca-certificates? will special devices in /dev, like /dev/urandom work in this case?
 
Yeah, that sucks too. But we could maybe fix $_SERVER variables with a custom extension.

I don't know how it's implemtned, but there should not be any fd outside the chroot if the same path exists inside the jail. It fails to set the session if the path is invalid, so I think it doesn't care and works with that what the kernel provides?

What's the problem with ca-certificates?

Could we not just mount /dev/urandom as fallback?
 
What's the problem with ca-certificates?
If you use any curl or fopen() to an https:// URL in your PHP scripts, these will most likely not work properly anymore.
And nowadays any CMS does use that extensively for stuff like checking for and installing updates.
 
It should work if /etc/ssl/certs is correctly mounted or what ever path is specified - we can also set a custom path
 
So I got some time to trace the FPM - and get phpinfo() working:

If enabled, the Plesk handler in nginx configuration file (vhosts/system/<domain>/nginx.conf) will take precedence and you will have no luck to inject the fastcgi_param as shown at top because it's overwritten by include of /etc/nginx/fastcgi.conf and any directive you will set in the Plesk backend set is only included at the bottom at the config file by including ./vhost_nginx.conf You would need to edit it manually and lock the file. So that sucks.

Anyway, I just decided to test it with *.phpc instead to bypass the standard handlers.
Note that I want to configure a dedicated FPM so pool settings will be applied from Plesk backend.

My script testfile is located in /var/www/vhosts/<domain>/httpdocs
Replace <domain> with your subscription/domain

Nginx-directive may look like that:

NGINX:
location ~ \.phpc$ {
    fastcgi_read_timeout 90;
    fastcgi_split_path_info ^((?U).+\.phpc)(/?.+)$;
    try_files $uri $fastcgi_script_name =404;
    fastcgi_pass "unix:/var/www/vhosts/system/<domain>/php-fpm.sock";
    include /etc/nginx/fastcgi.conf;
    # important: overwrite the variables only after fastcgi.conf
    fastcgi_param PATH_INFO $fastcgi_path_info;
    fastcgi_param SCRIPT_FILENAME /httpdocs$fastcgi_script_name;
}


INI:
; we need to tell PHP the new path: 
doc_root = /httpdocs

[php-fpm-pool-settings]
; we need to allow a new extension explicitly
security.limit_extensions = .phpc

; just enable chroot for PHP - set it to subscription path or {WEBSPACEROOT}
chroot = /var/www/vhosts/<domain>
 
You would need to edit it manually and lock the file. So that sucks.
I configured the vHost template generator to include the correct SCRIPT_FILENAME if a chroot setting was found in PHP configuration. We just need to set this variable and chroot login to get a basically working setup:

/opt/psa/admin/conf/templates/default/domain/service/fpm.php:
PHP:
<?php
/**
 * @var Template_VariableAccessor $VAR
 * @var array $OPT
 */
?>
        fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
        try_files $uri $fastcgi_script_name =404;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_pass "<?php echo $VAR->domain->physicalHosting->fpmSocket ?>";
        include /etc/nginx/fastcgi.conf;
        
        <?php /* chroot auto configuration begin */
        $phpCfgText = $VAR->domain->physicalHosting->getPhpSettingsConfigurationText();
        $isChrooted = preg_match('#php_admin_value\s+chroot\s+#', $phpCfgText) > 0;
        
        $docRoot = substr($VAR->domain->physicalHosting->getHttpsDir(), strlen($VAR->domain->physicalHosting->getVhostDir()));

        if(preg_match('#php_admin_value\s+doc_root\s+"([^"]+)"#', $phpCfgText, $matches)) {
            $docRoot = $matches[1];
        }
        
        if($isChrooted): ?>
        # chroot auto configuration
        fastcgi_param SCRIPT_FILENAME <?= $docRoot ?>$fastcgi_script_name;
        <?php endif /* chroot auto configuration end */ ?>

<?php if ($OPT['nginxCacheEnabled'] ?? true): ?>
    <?=$VAR->includeTemplate('domain/service/nginxCacheFastCgi.php', $OPT)?>
<?php endif ?>

The only hint that setup now needs is the PHP configuration:
INI:
; doc_root now automatically set, but it can be overwritten
; doc_root = /httpdocs

[php-fpm-pool-settings]
chroot = {WEBSPACEROOT}
 
Back
Top