When working with services that utilize PrivateTmp
(like php-fpm), you encounter a namespace isolation issue where the service gets its own private /tmp
directory rather than accessing the system-wide one. This becomes problematic when external processes (like deployment agents) write files to the global /tmp
that your PHP application needs to access.
Attempting to create symlinks (like /var/temp → /tmp
) fails because:
// This won't work due to namespace isolation
symlink('/tmp', '/var/temp');
The private namespace prevents traversal outside its sandbox, even through symlinks or bind mounts.
1. Direct Path Specification
The most reliable approach is having your deployment agent write files to php-fpm's private tmp location. Find your instance's private tmp:
systemctl show php-fpm.service | grep PrivateTmp
# Typically looks like /tmp/systemd-private-*-php-fpm.service-*/tmp
2. Shared Directory Approach
Create a world-readable directory outside /tmp:
sudo mkdir /var/shared-tmp
sudo chmod 1777 /var/shared-tmp
Then configure both your deployment agent and PHP to use this location.
3. Systemd Override (Advanced)
For systemd services, you can create an override:
sudo systemctl edit php-fpm.service
[Service]
PrivateTmp=false
Note: This disables the security benefit of PrivateTmp.
Here's how to safely check both locations:
function getGlobalTempFile($filename) {
$locations = [
'/tmp/' . $filename,
'/var/tmp/' . $filename,
'/var/shared-tmp/' . $filename,
// Add fallback paths as needed
];
foreach ($locations as $path) {
if (file_exists($path) && is_readable($path)) {
return $path;
}
}
return false;
}
When accessing shared temp locations:
- Validate file permissions (use
is_readable()
) - Implement proper file ownership checks
- Consider using
tempnam()
with a shared directory for new files
For high-security environments, consider using named pipes instead of temp files:
mkfifo /var/shared-pipe
This provides inter-process communication without file storage.
When working with php-fpm configurations that have PrivateTmp enabled, you're essentially working in an isolated filesystem namespace. This security feature creates a private /tmp directory for each service, typically mounted at paths like:
/tmp/systemd-private-abc123-php-fpm.service-xyz456/tmp
The deployment agent writes to the global /tmp, but your PHP script can't see it because it's looking at its private namespace. Symbolic links won't work because they're still resolved within the private namespace.
Here are several approaches to consider:
1. Disable PrivateTmp (Not Recommended)
The simplest but least secure option:
# In your service file (/lib/systemd/system/php-fpm.service)
PrivateTmp=false
2. Use Explicit Path Resolution
Access the global /tmp through its device/inode:
$globalTmp = '/tmp';
if (is_link('/tmp')) {
$globalTmp = readlink('/tmp');
}
$filePath = $globalTmp . '/deployment_file.txt';
3. Configure Shared Directory
Create a shared directory with proper permissions:
# As root:
mkdir /var/shared_tmp
chmod 1777 /var/shared_tmp
# In PHP:
file_get_contents('/var/shared_tmp/deployment_file.txt');
4. Systemd Override with Bind Mount
Create a systemd override to bind mount specific paths:
# /etc/systemd/system/php-fpm.service.d/override.conf
[Service]
PrivateTmp=true
BindReadOnlyPaths=/tmp/deployment_files:/tmp/external_files
The most robust approach combines systemd configuration with PHP path resolution:
// PHP code to handle both scenarios
function getGlobalTempPath($relativePath) {
$possiblePaths = [
'/tmp/' . $relativePath,
'/var/tmp/' . $relativePath,
'/tmp-system/' . $relativePath // Custom shared directory
];
foreach ($possiblePaths as $path) {
if (file_exists($path)) {
return $path;
}
}
throw new RuntimeException("File not found in any temp location");
}
$configFile = getGlobalTempPath('app_config.json');
Remember to set appropriate file permissions and consider security implications when accessing files across isolation boundaries.