Accessing System /tmp Files When PrivateTmp is Enabled in php-fpm


4 views

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.