Linux Setup
Linux is the most common environment for deployed PHP applications, and it is also a common local development environment through native Linux, WSL, containers, and dev containers.
The useful skill is not memorising one distribution's package names. It is proving which PHP runtime is active, where configuration is loaded from, which extensions are enabled, and whether CLI PHP and PHP-FPM match the project.
Package Managers And PHP Versions
Linux distributions install PHP through package managers.
Debian and Ubuntu use apt:
apt-cache policy php
php -v
Fedora, RHEL, and similar systems use dnf:
dnf list installed "php*"
php -v
Arch uses pacman:
pacman -Qs php
php -v
Use the package manager to understand what is installed, but use php -v to prove what your shell actually runs.
Find The Active Binary
Check the active CLI PHP binary:
which php
which -a php
php -v
which -a php is useful when several PHP binaries are visible on PATH. The first one is the one your shell runs.
If the project runs in Docker, WSL, or a remote server, run these checks inside that environment. Your host machine's PHP may be irrelevant.
Configuration Paths
Linux package layouts vary by distribution. Debian-style systems often use versioned directories such as:
/etc/php/8.3/cli/php.ini
/etc/php/8.3/fpm/php.ini
/etc/php/8.3/mods-available
/etc/php/8.3/cli/conf.d
/etc/php/8.3/fpm/conf.d
Other distributions may use paths such as:
/etc/php.ini
/etc/php.d
The reliable command is:
php --ini
Also check:
php -i | grep "Loaded Configuration File"
php -i | grep "Scan this dir"
CLI and FPM may load different php.ini files. A setting can be correct for terminal commands and wrong for web requests.
Extensions
List loaded extensions:
php -m
php -m | grep mbstring
php -m | grep pdo
Common project extensions include:
mbstring
intl
pdo
pdo_mysql
curl
zip
gd
opcache
redis
From PHP:
<?php
declare(strict_types=1);
foreach (['mbstring', 'intl', 'pdo', 'opcache'] as $extension) {
echo $extension . ': ';
echo extension_loaded($extension) ? 'loaded' : 'missing';
echo PHP_EOL;
}
// Prints:
// mbstring: loaded
// intl: loaded
// pdo: loaded
// opcache: loaded
If an extension is installed but not loaded, inspect the .ini files under the active configuration scan directory.
PHP-FPM Services
Production-style Linux PHP commonly uses PHP-FPM behind Nginx, Apache, or Caddy.
Check services with systemd:
systemctl status php-fpm
systemctl status php8.3-fpm
systemctl list-units "php*"
The exact service name depends on the distribution and PHP version.
Check FPM configuration and pools:
php-fpm -v
php-fpm -tt
On some systems the command may be versioned, such as php-fpm8.3.
Do not assume CLI PHP and FPM are the same runtime. Check both when debugging web-only problems.
Permissions And Users
Linux services run as users. PHP-FPM may run as www-data, nginx, apache, or a project-specific user.
Permission bugs often look like application bugs:
- uploads fail
- cache cannot be written
- logs do not appear
- sessions fail
- generated files are owned by the wrong user
Useful checks:
whoami
id
ps aux | grep php-fpm
ls -la storage
The user running CLI commands may not be the same user running web requests.
Composer Platform Checks
Run the project-level platform check:
composer check-platform-reqs
If this fails, compare:
which php
php -v
php -m
php --ini
Composer errors about missing extensions usually mean the active CLI runtime does not match the project requirements.
Let One Package Source Own The Runtime
PHP's official Unix installation guidance recommends distribution packaging systems as a normal installation path and notes that third-party repositories can provide more versions. Choose deliberately: mixing distribution PHP packages, third-party repositories, source builds, and copied binaries can produce incompatible modules and unclear update ownership.
Before installing or upgrading, identify the distribution and package source:
cat /etc/os-release
uname -m
Then inspect packages with the distribution's own tools. Package names and supported PHP branches change, so use current distribution documentation rather than copying an old command blindly.
A third-party repository can be appropriate when the distribution does not provide a required supported branch, but record why it is trusted, how signing keys are managed, and which packages it replaces. Do not enable several competing PHP repositories merely to make dependency resolution succeed.
Install extensions through the same package source as the selected PHP runtime whenever possible. A module built for another PHP API, architecture, or distribution library set may fail to load.
Distinguish Installed Packages From Active Processes
A package manager reports what is installed. command -v php and php -v report the CLI command selected by the current shell. systemctl reports service state. These are different facts.
Use shell-native command discovery:
command -v php
type -a php
php -v
A server may have PHP CLI installed without PHP-FPM, or several FPM services installed while only one is enabled. A container can run FPM without exposing any host php command. Always collect evidence in the environment that owns the workload.
After package changes, restart affected long-running services. A new CLI invocation reads the new executable immediately, but an existing FPM master process continues running until reloaded or restarted through its service manager.
Inspect Service State And Logs Together
systemctl status provides service state and recent messages on systemd distributions. Use the exact discovered service unit:
systemctl list-unit-files | grep -i php
systemctl list-units --type=service | grep -i php
systemctl status php8.3-fpm
journalctl -u php8.3-fpm --since today
Do not assume php8.3-fpm is the correct name; distributions use different naming conventions. A service can be installed but disabled, active but repeatedly restarting, or healthy while the web server points to another socket.
Before restarting a production service, validate its configuration with the binary or package command supported by that installation. A forced restart of invalid configuration can turn a partial problem into an outage. In production, follow the deployment and change-control process rather than experimenting directly.
Trace The Web Request Boundary
For PHP-FPM, inspect the pool configuration and the web server's FastCGI target. They must agree on a Unix socket path or TCP address.
Useful evidence includes:
ps -ef | grep '[p]hp-fpm'
ss -lxp | grep php
ss -ltnp | grep php
The FPM master and workers can run as different configured users. The web server must be able to reach the socket, and FPM workers must have the intended access to application files, cache directories, sessions, uploads, logs, and external services.
Do not make an FPM socket or application directory world-writable as a generic fix. Correct ownership, group membership, directory traversal permissions, socket mode, or pool configuration at the narrow boundary that is failing.
Use Least Privilege For Project Files
Run Composer, tests, and normal application commands as the project developer or controlled container user, not as root. Running Composer with sudo can create root-owned vendor/, cache, or generated files that later fail under both the developer and web user.
Inspect path permissions component by component:
namei -l /path/to/project/storage
stat /path/to/project/storage
namei -l is useful because access requires permission on every parent directory, not only the final file. Apply the project's documented ownership and mode rather than recursively granting broad write access.
When a service can read a file but cannot write a cache or upload, check the exact service user and target directory. When CLI succeeds but FPM fails, reproduce under the service boundary or inspect service logs instead of changing application logic first.
Remember Mandatory Access Controls
Traditional Unix owner/group/mode permissions are not the only access controls. SELinux, AppArmor, systemd sandboxing, container confinement, and read-only mounts can deny an operation that ordinary permission bits appear to allow.
On SELinux systems, inspect status and relevant audit events with distribution-supported tools. Do not disable SELinux globally to confirm a guess. Identify the denied operation and apply an appropriate label or policy through documented mechanisms.
Similarly, an AppArmor profile or systemd service hardening option can restrict paths, networking, or system calls. Treat the denial log as evidence and preserve the security boundary whenever possible.
Compare CLI And FPM Without Publishing Secrets
A focused temporary web diagnostic can report PHP_VERSION, PHP_SAPI, loaded INI path, and selected extension presence. Remove it after use. Do not leave phpinfo() publicly reachable: it can expose versions, paths, environment data, headers, and configuration.
For CLI, gather the same fields directly. Compare facts rather than assuming package names imply parity. CLI and FPM can use separate INI trees, scanned module files, environment values, users, and working directories even when they come from the same package branch.
Treat Containers, WSL, And Remote Hosts As Separate Linux Systems
A Linux host, WSL distribution, development container, application container, and remote server each have their own packages, filesystems, users, processes, and configuration. Run checks inside the boundary where PHP executes.
For containers, prefer image definitions over manual package installation in a running container. An interactive fix disappears when the container is recreated. Record the base image, installed extensions, configured user, health check, and rebuild process.
For WSL, Windows PHP and WSL PHP are independent. Store and run the project in the environment recommended by the team, and be conscious of filesystem performance and permissions across mounted Windows paths.
For remote production hosts, do not expose diagnostics or make package changes without authorization, backups, rollback, and observability.
Linux-Specific Failure Patterns
| Symptom | First boundary to inspect |
|---|---|
Package installed but php missing |
Package contents, shell PATH, and current environment |
| CLI works but website returns 502 | FPM service, socket/listener, and web-server target |
| Extension loads in CLI only | Separate CLI and FPM INI scan directories |
| Cache or upload write fails | FPM user, parent-directory permissions, and mandatory access controls |
| Service restarts repeatedly | Configuration validation and journal logs |
| Host works but container fails | Image packages, container user, mounts, and environment |
| Composer files become root-owned | Development commands were run with unnecessary elevation |
A good Linux diagnosis names the failing process, package owner, configuration source, service user, and log evidence. "Linux permissions" or "PHP-FPM issue" is not yet a diagnosis.
Linux Setup Checklist
For a Linux setup note, record:
- Distribution and version.
- Whether PHP runs on the host, in WSL, in Docker, or on a remote server.
- What
which phpandphp -vreturn. - What
php --inireturns. - Which extensions are required.
- Whether PHP-FPM is used and what the service is called.
- Which user runs PHP-FPM.
- Whether
composer check-platform-reqspasses.
This checklist gives you enough information to debug setup, extension, and permission issues without guessing.
What You Should Be Able To Do
After this lesson, you should be able to verify Linux PHP from the terminal, inspect configuration paths, check extensions, distinguish CLI from PHP-FPM, identify service users, and confirm Composer platform requirements.
For junior PHP work, this matters because Linux is where many PHP applications ultimately run. Knowing how to inspect the runtime makes you useful in both development and deployment conversations.
Practice
Practice: Produce A Linux PHP Environment Record
Create a reproducible setup record for PHP running on native Linux, WSL, a container, or a remote development host.
Requirements
Record:
- distribution, release, architecture, and runtime boundary;
- package source or container image that owns PHP;
- selected CLI binary, version, configuration, modules, and Composer platform result;
- installed and active PHP-related service units;
- PHP-FPM version, pool/listener, process user, and recent service logs when FPM is used;
- one CLI smoke test and one web/FPM smoke test;
- writable-directory ownership and permissions for a project cache or upload path.
State which commands require elevated read access and which development commands should remain unprivileged. Explain why host checks do not prove a container, WSL distribution, or remote server has the same environment.
Show solution
cat /etc/os-release
uname -m
command -v php
type -a php
php -v
php --ini
php -m
composer check-platform-reqs
Record the package source or container image rather than only the PHP path. For systemd-based FPM environments, discover before assuming names:
systemctl list-unit-files | grep -i php
systemctl list-units --type=service | grep -i php
systemctl status php8.3-fpm
journalctl -u php8.3-fpm --since today
ps -ef | grep '[p]hp-fpm'
ss -lxp | grep php
Replace php8.3-fpm with the discovered unit. Inspect the configured pool, listener, and worker user. Check a required writable path with:
namei -l /path/to/project/storage
stat /path/to/project/storage
Run syntax/execution checks as the normal project user and test the real web entry point through its configured server. Reading service logs or status may require authorised elevation; Composer and project scripts normally should not run as root.
The record is complete only for the environment where commands ran. Host output cannot establish container packages, WSL PHP, or a remote server's FPM configuration; gather equivalent evidence inside that boundary.
Task: Diagnose CLI And PHP-FPM Configuration Drift
A CLI script reports memory_limit=512M and loads intl, but the same application through Nginx reports memory_limit=128M and fails because intl is missing.
Requirements
- Identify the active CLI INI file and scanned directory.
- Discover the actual PHP-FPM service, binary, pool, and configuration tree.
- Confirm the web server's FastCGI socket or address matches that pool.
- Compare extension loading and resolved settings in each SAPI without leaving a public diagnostic page.
- Validate configuration before a controlled service reload or restart.
- Use service logs to verify the result.
- Explain why editing only the CLI INI cannot repair the web request.
Show solution
php --ini
php -r 'echo ini_get("memory_limit"), PHP_EOL;'
php --ri intl
Discover FPM rather than assuming its name:
systemctl list-units --type=service | grep -i php
ps -ef | grep '[p]hp-fpm'
ss -lxp | grep php
Inspect the discovered service unit, FPM version, pool files, and listener. Confirm Nginx's fastcgi_pass points to that socket or address. Use the installation's supported FPM configuration-test command before reloading it.
Create a temporary restricted diagnostic endpoint that reports only PHP_SAPI, php_ini_loaded_file(), the INI scan directory, memory_limit, and extension_loaded('intl'). Request it locally, record the result, and remove it immediately.
Enable or configure intl in the FPM SAPI's scanned configuration through the owning package system, then perform an authorised reload or restart. Check:
systemctl status <discovered-fpm-unit>
journalctl -u <discovered-fpm-unit> --since today
Retest the application. CLI and FPM are separate process contexts and can scan different INI directories, so changing CLI configuration alone cannot alter the already-running FPM workers.
Task: Diagnose A Linux Extension And Permission Failure
A PHP-FPM application cannot write its cache and reports that the Redis extension is unavailable. CLI PHP writes the cache and loads Redis successfully.
Requirements
- Compare CLI and FPM binaries, versions, INI scan directories, and extension state.
- Identify the FPM worker user and the exact cache path permissions, including parent directories.
- Check service logs and account for SELinux, AppArmor, systemd restrictions, or read-only mounts.
- Avoid
chmod -R 777, disabling security controls, or running Composer as root. - Define the narrow configuration, package, ownership, or policy change supported by the evidence.
- State the CLI, web, and service-log verification required afterward.
Show solution
command -v php
php -v
php --ini
php --ri redis
Discover and inspect the active FPM process and service:
ps -ef | grep '[p]hp-fpm'
systemctl list-units --type=service | grep -i php
journalctl -u <discovered-fpm-unit> --since today
Compare FPM's branch, configuration tree, and loaded Redis extension using service configuration or a temporary restricted diagnostic endpoint. Install or enable Redis through the package source or image that owns FPM, not merely the CLI package set.
Inspect the cache path:
namei -l /path/to/project/storage/cache
stat /path/to/project/storage/cache
Identify the configured FPM worker user and grant only the project-required owner/group access. If ordinary permissions look correct, inspect mandatory access-control or service-confinement logs instead of disabling those controls globally.
Do not use chmod -R 777: it grants unnecessary access and can hide the real missing parent permission, group, label, or mount mode. Do not run Composer as root, which can create more inaccessible files.
After the narrow fix, verify Redis in both CLI and web contexts, write and read a cache entry through the application, confirm correct file ownership, and review service logs for remaining startup or access denials.