In short: open_basedir limits the directories PHP may access. On its own, that is only an additional barrier. Effective isolation per website arises only through the combination: own system user, own PHP-FPM pool, own temp folder and own database credentials. A break-in on one website then stays limited to that website.
Why isolation decides the damage after PHP2Shell
On many servers several websites run side by side: the company site, a shop, an old campaign site, and for agencies often dozens of client projects. They frequently share one system user and one PHP process. If one of them is compromised, all the others are exposed.
PHP2Shell makes this risk tangible. The vulnerabilities CVE-2026-109618 and CVE-2026-109619 allow remote code execution without authentication, regardless of the CMS. According to media reports, a first attack attempt occurred as early as 17 October 2026, and the patches followed on 21 October. The fix is the update. Whether an attack in the period before it hits one website or ten, however, is decided by isolation.
For the second vulnerability in particular, a further thought applies: according to the PHP project, internal state of a request can persist in a worker and be used by a later request in the same worker. Separate pools per website ensure that websites do not share workers in the first place.
What open_basedir achieves
With open_basedir you define a list of directories. PHP's file functions such as fopen, include or file_get_contents then refuse access to anything outside this list. A planted script can then not simply read the configuration files of the neighbouring website.
The limits are important: the check takes place inside PHP. Programs started outside PHP are not subject to it, and flaws in the interpreter can bypass it. That is why open_basedir always belongs together with blocked system functions, as described in the guide Using disable_functions correctly, and above all with separation at the operating system level.
Isolation per website in four layers
A resilient separation builds on several layers. Each catches what the previous one does not achieve.
| Layer | Measure | Effect |
|---|---|---|
| Operating system | own system user per website | Files of other websites are not readable |
| PHP processes | own PHP-FPM pool with its own socket | No shared worker, own settings |
| PHP configuration | open_basedir, own tmp and session folder |
Additional barrier, no shared temp files |
| Data | own database user with rights only on its own database | No access to other customers' data |
With high protection needs, for example shops with payment processing or portals with health data, containers or separate servers are added.
Configuring open_basedir in the PHP-FPM pool
The following example shows a pool for a website with its own user seite1. Adjust paths and version number to your system.
; /etc/php/8.3/fpm/pool.d/seite1.conf
[seite1]
user = seite1
group = seite1
listen = /run/php/seite1.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = ondemand
pm.max_children = 10
php_admin_value[open_basedir] = /var/www/seite1/:/var/www/seite1-tmp/
php_admin_value[upload_tmp_dir] = /var/www/seite1-tmp/
php_admin_value[sys_temp_dir] = /var/www/seite1-tmp/
php_admin_value[session.save_path] = /var/www/seite1-tmp/sessions
php_admin_value[error_log] = /var/log/php/seite1.error.log
php_admin_flag[session.upload_progress.enabled] = off
The last line sets the PHP2Shell interim measure from php.net at the same time. It takes effect only from the pool, php.ini or server configuration, not via ini_set() or .user.ini. You create the folders with suitable permissions:
sudo useradd --system --home /var/www/seite1 --shell /usr/sbin/nologin seite1
sudo mkdir -p /var/www/seite1-tmp/sessions
sudo chown -R seite1:seite1 /var/www/seite1-tmp
sudo chmod 700 /var/www/seite1-tmp
What the permissions in the actual web directory should look like is described in the guide File permissions on the web server.
Connecting the web server and checking the result
In Nginx, each website points to its own socket. That way no request ends up in another website's pool.
server {
server_name seite1.example;
root /var/www/seite1/public;
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/seite1.sock;
}
}
More notes on this combination are at Securing Nginx with PHP-FPM. After the restart, check that the pools really run separately:
sudo systemctl restart php8.3-fpm
# Each pool should run under its own user
ps -eo user,pid,cmd | grep "[p]hp-fpm: pool"
Then test all functions of the website: uploads, image processing, caches, scheduled tasks. Error messages in the error log that mention open_basedir restriction show which path is still missing. Add only paths the application really needs, and never the entire file system.
Agencies with many client sites should maintain the pool configuration as a template. That way every new website gets the separation automatically instead of having to retrofit it later. Background for this case is at Security partner for agencies.
Typical stumbling blocks during the changeover
The changeover to separate pools can be planned but has a few recurring pitfalls. The most common concerns shared libraries: if, for example, a framework or a folder of fonts lies outside the website directory, this path must be added to open_basedir as read access. It is better to bring such dependencies into the project itself.
The second pitfall is sessions. If the storage location changes, all visitors are logged out once and running shopping carts are lost. Plan the changeover in a quiet time window and announce it internally.
Third, scheduled tasks often run via the command line and thus under a different user than the website. Check cron jobs and queues and have them run under the new website user, otherwise files with the wrong owner arise that PHP may later not be allowed to change.
Finally, each pool should have its own limits for processes and memory. A website that comes under load or is attacked then cannot bring the others to their knees.
How does PHP SOS help?
- We establish which websites currently share users, processes and databases.
- We set up separate users, pools, temp folders and database credentials for each website, without changing the applications.
- We test each website after the changeover and fix missing paths in a targeted way.
- We document the separation as a template for future projects.
How this fits into an overall concept is described in the service Server hardening for PHP web servers.
Frequently asked questions
Is open_basedir enough to separate websites from each other?
No. open_basedir is an additional barrier inside PHP, not a hard separation. Isolation becomes reliable only with separate system users and pools per website, and with containers or separate servers for higher protection needs.
Why do uploads stop working after setting open_basedir?
Usually the folder for temporary files is missing from the list. Create a separate tmp folder for each website, add it to open_basedir and set upload_tmp_dir and sys_temp_dir to this folder.
Can an application lift open_basedir itself?
At runtime an application can only tighten the setting further, not loosen it. Still, set open_basedir via php_admin_value in the pool so that the value is unambiguously fixed.
Is isolation also possible with shared hosting?
That is up to the hosting provider. Many offers separate customers via their own users and PHP processes. Ask how the separation is implemented, especially if several of your websites are in one package.