Installing and Configuring PHP on Windows, Linux, and macOS
Install PHP on any major operating system, verify the installation from the command line, and understand how to configure PHP for local development versus production, including web server integration and Docker-based setups.
Before writing any real PHP application you need a working PHP environment: the interpreter itself, a way to run or serve scripts, and an editor configured to help you write correct code. This lesson walks through installing PHP on Windows, Linux, and macOS, verifying the install, understanding php.ini, and the different ways to actually serve PHP pages — from the built-in development server to full Apache/Nginx/PHP-FPM stacks and Docker containers.
Installing PHP per Platform
On Windows, the most common approaches are: download the official Windows binaries from windows.php.net (a zip you extract and add to PATH manually), or install a bundled stack like XAMPP, WAMP, or Laragon which packages PHP together with Apache/Nginx, MySQL, and useful tools in one installer — Laragon in particular is popular with modern PHP developers for how lightweight and fast it is to switch PHP versions. Alternatively, Windows users can install PHP inside WSL2 (Windows Subsystem for Linux) and follow the Linux instructions, which is increasingly the recommended path since it matches production Linux environments closely.
On Linux, PHP is available through every major distribution's package manager. On Debian/Ubuntu-based systems: sudo apt update && sudo apt install php php-cli php-fpm php-mbstring php-xml php-mysql. On Fedora/RHEL-based systems: sudo dnf install php php-cli php-fpm. On Arch: sudo pacman -S php. Because distro repositories often lag behind the latest PHP release, many teams add a third-party repository (like Ondřej Surý's PPA on Ubuntu, ppa:ondrej/php) to get access to the newest PHP 8.x versions and to install multiple PHP versions side by side.
On macOS, Apple ships an older PHP build with the OS (and stopped bundling PHP entirely in recent macOS versions), so the standard approach is Homebrew: brew install php installs the latest stable PHP and its CLI, and brew install [email protected] (for example) lets you pin or install a specific version. Homebrew also makes it straightforward to switch active PHP versions with brew link --overwrite [email protected] when multiple versions are installed side by side.
• Windows: official zip build, XAMPP/WAMP/Laragon bundle, or PHP inside WSL2. • Linux: distro package manager (apt, dnf, pacman) — add a PPA/COPR for the newest versions. • macOS: Homebrew (brew install php) is the standard, actively maintained path. • For consistency with production, many teams standardize on Docker or WSL2 regardless of host OS.
PATH and Verifying the Installation
For the php command to work from any terminal location, the directory containing the php executable must be listed in your system's PATH environment variable. Package managers (apt, dnf, Homebrew) configure this automatically; manual zip installs on Windows require adding the PHP folder to PATH yourself via System Properties → Environment Variables, or the setx PATH command.
Once installed, three commands confirm everything is wired up correctly. php -v prints the installed PHP version, build date, and Zend Engine version — this is the fastest sanity check that PHP is on PATH and runnable. php --ini prints the paths PHP is using for its configuration: the loaded php.ini file (or 'none' if not found) and the additional .ini files scanned directory. php -m lists every extension currently compiled in or loaded, which is essential when a script fails because a needed extension (like pdo_mysql, mbstring, or gd) isn't enabled.
It's common on a fresh install for php -m to be missing something a project needs. On Linux, missing extensions are usually a separate package to install (e.g. sudo apt install php-mbstring); on Windows/macOS, missing extensions are usually enabled by uncommenting an extension= line in php.ini and restarting whatever is serving PHP.
• php -v — confirms PHP is installed and shows its version. • php --ini — shows which php.ini file (if any) is actually loaded. • php -m — lists loaded/compiled-in extensions. • PATH must include the PHP binary's directory for php to work from any folder.
Serving PHP: Built-in Server, Apache, Nginx, PHP-FPM
For local development, PHP ships with a zero-configuration built-in web server. Running php -S localhost:8000 inside a project folder starts a lightweight server that serves that directory over HTTP — perfect for quick testing, but not intended for production (it's single-threaded and lacks the hardening of a real web server). This is usually the fastest way to preview a small PHP project.
In production, PHP is almost always paired with a dedicated web server. Apache can run PHP either as an embedded module (mod_php, simple but less scalable) or via PHP-FPM through a proxy handler (mod_proxy_fcgi). Nginx does not run PHP itself at all — Nginx only serves static files and reverse-proxies dynamic requests to PHP-FPM over a Unix socket or TCP port (commonly 127.0.0.1:9000 or a .sock file), configured with an fastcgi_pass directive and a location ~ \.php$ block.
PHP-FPM (FastCGI Process Manager) manages pools of PHP worker processes, each of which handles one request at a time, and recycles workers to avoid memory leaks accumulating over long uptimes. FPM pool settings — pm.max_children, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers — control how many PHP processes exist to handle concurrent traffic, and tuning them correctly is one of the most common PHP production-performance tasks.
• php -S localhost:8000 — instant local dev server, not for production. • Apache: mod_php (embedded) or PHP-FPM via mod_proxy_fcgi. • Nginx: never runs PHP itself — always proxies to PHP-FPM over socket/TCP. • PHP-FPM pool settings (pm.max_children etc.) control worker concurrency in production.
Docker, IDE Setup, and php.ini: Dev vs Production
Docker has become the most reproducible way to run PHP, because the exact PHP version, extensions, and OS-level dependencies are pinned in a Dockerfile shared by every developer and the production servers alike, eliminating "works on my machine" drift. The official php Docker images come in flavors like php:8.3-fpm, php:8.3-apache, and php:8.3-cli; a typical local setup uses docker-compose.yml to wire together a PHP-FPM container, an Nginx container, and a database container.
For an IDE, PHPStorm is the most feature-complete dedicated PHP IDE (debugging, refactoring, deep framework support); VS Code with the "PHP Intelephense" or "PHP" extensions plus Xdebug integration is the most popular free alternative. Whichever editor is used, configuring Xdebug (a PHP extension for step-debugging and profiling) is one of the highest-value setup steps for serious development.
php.ini is PHP's main configuration file, controlling things like error reporting, memory limits, upload sizes, timezone, and which extensions load. PHP ships two reference files, php.ini-development and php.ini-production, that show the recommended defaults for each context. Development configuration favors visibility: display_errors = On, error_reporting = E_ALL, generous memory_limit, and opcache.validate_timestamps = 1 so code changes are picked up immediately. Production configuration favors safety and speed: display_errors = Off (errors go to a log instead, never to visitors), tighter memory_limit, and opcache.validate_timestamps = 0 with periodic cache resets on deploy for maximum performance.
• Docker pins exact PHP version + extensions for reproducible dev/prod parity. • IDE: PHPStorm (full-featured) or VS Code + Intelephense/PHP extension + Xdebug. • php.ini controls error reporting, memory limits, timezone, extensions, and more. • Dev config: display_errors On, E_ALL, live opcache validation. • Prod config: display_errors Off (log instead), tuned memory limits, opcache.validate_timestamps=0 for speed.
Verifying a PHP Installation
-t public/ tells the built-in server to treat the public/ folder as the document root. php -m output is piped through grep to check a single extension is loaded.
php -v
php --ini
php -m | grep -i mbstring
php -S localhost:8000 -t public/Code Examples
These three commands together confirm the PHP version, exactly which configuration file is active, and which extensions are available — the first thing to check whenever a PHP script behaves unexpectedly in a new environment.
php -v && php --ini && php -m The built-in server needs zero configuration — it serves the current directory and routes requests to matching.php files, making it the fastest way to try out a small script or prototype. Expected result: PHP 8.3.6 Development Server (http://localhost:8000) started
Pinning the base image tag (php:8.3-fpm) and explicitly installing extensions with docker-php-ext-install guarantees every environment — a teammate's laptop, CI, and production — runs an identical PHP setup. This container would be paired with a separate Nginx container that proxies .php requests to it over port 9000.
$ php -v
PHP 8.3.6 (cli) (built: Apr 10 2024 12:00:00) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.6, Copyright (c) Zend Technologies
$ php --ini
Configuration File (php.ini) Path: /etc/php/8.3/cli
Loaded Configuration File: /etc/php/8.3/cli/php.ini
Scan for additional .ini files in: /etc/php/8.3/cli/conf.d
Additional .ini files parsed: /etc/php/8.3/cli/conf.d/10-mysqlnd.ini,
/etc/php/8.3/cli/conf.d/10-opcache.ini
$ php -m | head -5
[PHP Modules]
Core
ctype
curl
date$ mkdir myapp && cd myapp
$ echo '<?php echo "It works! PHP " . phpversion();' > index.php
$ php -S localhost:8000FROM php:8.3-fpm
RUN docker-php-ext-install pdo_mysql mbstring
WORKDIR /var/www/html
COPY . .Local Dev Stack vs Production Stack
The built-in server is fine for a quick check; anything real is served through Nginx/Apache + PHP-FPM.
Local dev (quick): Production:
┌───────────────────┐ ┌────────┐ proxy ┌───────────┐
│ php -S localhost │ │ Nginx │ ────────▶ │ PHP-FPM │
│ :8000 │ │(static)│ :9000 │ (pool of │
└───────────────────┘ └────────┘ │ workers) │
└───────────┘Important Rules
Always verify a new environment with php -v, php --ini, and php -m before debugging application code.
Never leave display_errors On or error_reporting E_ALL in a production php.ini.
Use Docker or WSL2 to keep local PHP versions consistent with production.
Pin PHP and extension versions explicitly rather than relying on 'whatever is installed'.
Restart PHP-FPM (or the built-in server) after any php.ini change — settings are read at process start.
Dev/Prod Parity with Docker
Many PHP teams commit a docker-compose.yml alongside their codebase so that any new developer can run docker compose up and get the exact same PHP version, extensions, and Nginx configuration that production uses, eliminating an entire category of 'works on my machine' bugs.
sudo systemctl restart php8.3-fpm # or the equivalent for your OS php.ini is read once when a PHP-FPM worker process starts; changes to the file have no effect until the FPM service (or the built-in server) is restarted.
Trying to serve a production site with php -S Serve production traffic through Nginx/Apache + PHP-FPM.
The built-in development server is single-threaded and not hardened for concurrent or hostile traffic; it exists purely for local development convenience.
Which command shows exactly which php.ini file PHP is currently using? A. php -v B. php --ini C. php -m D. php -S
B. php --ini — php --ini prints the loaded configuration file path along with any additional scanned .ini files.
In production, what should display_errors be set to, and why? A. On, so admins can see errors immediately in the browser B. Off, so errors are logged instead of shown to visitors C. It has no effect in production D. It should be removed from php.ini entirely
B. Off, so errors are logged instead of shown to visitors — Showing raw errors to visitors can leak sensitive internals and looks unprofessional; production should log errors (error_log) while display_errors stays Off.
What is the role of Nginx in a typical Nginx + PHP-FPM production setup? A. It executes the PHP code directly B. It only serves static files and proxies .php requests to PHP-FPM C. It replaces PHP-FPM entirely D. It compiles PHP into machine code
B. It only serves static files and proxies .php requests to PHP-FPM — Nginx cannot execute PHP itself; it serves static assets and forwards PHP requests to a PHP-FPM pool via FastCGI.
On your machine, install PHP for your OS, then run the three verification commands (php -v, php --ini, php -m) and confirm the mbstring extension is listed. Finally, start the built-in server on port 8080 for a folder containing a single index.php that echoes phpversion().
php -v php --ini php -m | grep -i mbstring php -S localhost:8080
Use php -m | grep -i mbstring (or findstr on Windows) to search the extension list quickly.