PHP (8.5) Module 1 – Start here: PHP in 2026
Install PHP 8.5 and check your setup
Install PHP 8.5 on Windows, macOS or Linux, check it with php -v, php -m and php --ini, and set up a development php.ini that shows and logs errors.
What you will learn
- Choose a way to install PHP 8.5 on Windows, macOS or Linux, or in a container, that keeps getting security fixes
- Inspect an installation's version, build type and extensions with php -v and php -m
- Find which php.ini file PHP reads with php --ini, and in which order it looks for one
- Create a development php.ini that shows and logs every error and sets a time zone
Before you start
What you’ll learn · Before you start
What you will learn
- Choose a way to install PHP 8.5 on Windows, macOS or Linux, or in a container, that keeps getting security fixes
- Inspect an installation's version, build type and extensions with php -v and php -m
- Find which php.ini file PHP reads with php --ini, and in which order it looks for one
- Create a development php.ini that shows and logs every error and sets a time zone
Before you start
On this page
There are five common ways to put PHP on a computer, and they differ in what matters most later: whether security fixes reach you, and which branch you get at all. Ubuntu 24.04’s own package installs PHP 8.3, for example, and the newest XAMPP bundle for Windows ships PHP 8.2.12, a branch whose security fixes end with 2026.
This lesson helps you pick an install for your computer, then checks it the way you will check every PHP setup from
now on: the version and the build with php -v, the extensions with php -m, the settings file with php --ini,
and finally a development php.ini that shows and logs every error.
Choose an install that keeps getting security fixes
Five ways cover most setups. Each one gives you the same PHP language, but who updates it, and to which branch, is different:
- Windows: the zip from php.net. Pick the VS17 x64 Non Thread Safe build. The download page says Non Thread Safe
builds are for the command line and FastCGI, and Thread Safe builds for PHP loaded as a module into a web server.
It also needs Microsoft’s Visual C++ Redistributable for Visual Studio 2015-2022. Unzip it to a folder such as
C:\php, add that folder to thePATHvariable without overwriting what is there, and copyphp.ini-developmenttophp.iniin the same folder. - macOS: Homebrew.
brew install phpinstalls the current branch, 8.5, with ready-built packages for Apple silicon, and puts its php.ini inetc/php/8.5under the Homebrew folder. Older branches have formulae of their own, such asphp@8.4. - Linux: the distribution’s packages. Check the branch before you install, with
apt policy phpordnf info php. Ubuntu 24.04 LTS ships 8.3, Ubuntu 26.04 LTS ships 8.5 and Debian 13 ships 8.4, and the distribution’s security team, not php.net, decides how long they are patched. - Containers: the official Docker image.
docker run --rm php:8.5-cli php -vstarts a clean PHP 8.5 without installing anything else. Tags pin the branch and even the patch release, such as8.5.11-cli, and the image comes incli,fpm,apacheandztsvariants. - A bundled stack. Installers such as XAMPP put Apache httpd, a database and PHP on a computer in one step. Their PHP is whatever the bundle ships, and XAMPP’s own FAQ says it is not meant for production use but only for development.
Three questions about your own setup decide most of the choice. Answer them, and the table marks the ways that fit and gives the reason under each one that does not:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Way to install | Security updates | Choice of branch | Adding extensions | Setup |
|---|---|---|---|---|
| Windows zip from php.net What you get: php.exe and its extensions in one folder | Mixed you download each new patch release yourself | Good every supported branch; x64 Non Thread Safe suits the command line | Mixed DLLs in the ext folder, each switched on with an extension= line | Mixed unzip, add the folder to PATH, copy a php.ini template |
| Homebrew on macOS What you get: brew install php, which is 8.5 | Good brew upgrade installs new patch releases | Good php is 8.5; php@8.4, php@8.3 and php@8.2 are separate formulae | Mixed the common ones are built in; others come from PIE or PECL | Good one command, with php.ini in etc/php/8.5 under the Homebrew folder |
| Linux packages What you get: the distribution's own PHP | Good security updates from the distribution, by its own policy | Poor one branch per release: Ubuntu 24.04 has 8.3, Debian 13 has 8.4 | Good one package per extension, such as php8.4-mbstring | Good one apt or dnf command |
| Docker image What you get: PHP in a container, such as php:8.5-cli | Mixed pull the new tag and rebuild your image | Good a tag for every branch and patch release, such as 8.5.11-cli | Mixed docker-php-ext-install builds them into your own image | Mixed needs Docker, and PHP runs inside the container |
| Bundled stack What you get: web server, database and PHP in one installer | Poor its PHP changes only with a new bundle; XAMPP's newest for Windows has 8.2.12 | Poor the branch the bundle ships, whatever is current | Good the common ones ready, switched on in its php.ini | Good one installer with Apache httpd and a database |
How the answers decide
- “Which system do you develop on?” Windows: rules out Homebrew on macOS and Linux packages. Homebrew and Linux packages are for macOS and Linux; on Windows they work only inside WSL, a Linux system that runs on Windows.
- “Which system do you develop on?” macOS: rules out Windows zip from php.net and Linux packages. The php.net zip is built for Windows and the packages for Linux, so neither runs on macOS.
- “Which system do you develop on?” Linux: rules out Windows zip from php.net and Homebrew on macOS. The php.net zip is built for Windows, and Homebrew is the macOS route; on Linux the distribution's packages or a container take their place.
- “Must your computer run the same PHP branch as a server you deploy to?” Yes, the same branch: adds a caveat to Linux packages and Bundled stack. They ship one fixed branch, which may not be the server's; a container tag pins the branch exactly.
- “Do you need several PHP branches side by side, one per project?” Yes, several: adds a caveat to Windows zip from php.net, Linux packages and Bundled stack. One branch at a time, unless you keep several folders or packages yourself; containers give each project its own.
In short: Homebrew on macOS, the php.net zip on Windows, the distribution’s packages on Linux when they ship the branch you need, and a container whenever you must match a server exactly or keep several branches side by side. A bundle is fine for a first look, as long as you know its branch and keep it off any network you do not control.
Checkpoint
Check: Choosing how to install PHP
Check the version and the extensions you installed
Whatever you installed, open a new terminal so that it sees the new PATH, and ask PHP what it is. The first command
names the version and the build, the second lists loaded extension: A module that adds functions and classes to PHP, such as mbstring, intl, pdo_sqlite or sodium. php -m lists the extensions an installation has loaded., filtered here to
the ones later lessons of this track rely on: PDO with its drivers, mbstring, intl and sodium:
# The commands of this session, as you would type them in a terminal; the output shows what they printed.
from session import run
run("""
php -v
php -m | grep -i -E 'pdo|mbstring|intl|sodium'
""") Output
$ php -v
PHP 8.5.11 (cli) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.5.11, Copyright (c) Zend Technologies
with Zend OPcache v8.5.11, Copyright (c), by Zend Technologies
$ php -m | grep -i -E 'pdo|mbstring|intl|sodium'
intl
mbstring
PDO
pdo_mysql
pdo_pgsql
pdo_sqlite
sodium
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 versions_session.py
The first line of php -v reads PHP 8.5.11 (cli) (NTS): the version, the SAPI in brackets, and the build type.
NTS is a NTS build: A Non Thread Safe build of PHP, the usual build for the command line and PHP-FPM. A Thread Safe (TS or ZTS) build is needed when PHP runs as a module inside a threaded web server., the one the command line and PHP-FPM use; a thread-safe
build says ZTS instead. The last line says OPcache, PHP’s cache of compiled scripts, is part of this build.
php -m prints every loaded module, one per line, and the session filtered it with grep. On Windows, findstr
does the same job. A script can check for itself, and the next program does: it prints the build, the settings file
PHP read, four settings and whether seven extensions are loaded. Run it on your own computer after you install:
<?php
// What this PHP is, which settings file it read, and whether the extensions this track uses are loaded.
printf("PHP %s, %s SAPI, %s\n", PHP_VERSION, PHP_SAPI, PHP_ZTS ? 'thread safe (ZTS)' : 'not thread safe (NTS)');
$ini = php_ini_loaded_file();
echo 'php.ini: ', $ini === false ? 'none loaded' : $ini, "\n";
echo "Settings:\n";
printf(" display_errors %s\n", ini_get('display_errors'));
printf(" error_reporting %s\n", error_reporting() === E_ALL ? 'E_ALL' : error_reporting());
printf(" log_errors %s\n", ini_get('log_errors') ? 'on' : 'off');
printf(" date.timezone %s\n", date_default_timezone_get());
echo "Extensions:\n";
foreach (['mbstring', 'intl', 'pdo_sqlite', 'sodium', 'openssl', 'dom', 'curl'] as $extension) {
printf(" %-11s %s\n", $extension, extension_loaded($extension) ? 'loaded' : 'MISSING');
} Output
PHP 8.5.11, cli SAPI, not thread safe (NTS) php.ini: none loaded Settings: display_errors 1 error_reporting E_ALL log_errors off date.timezone UTC Extensions: mbstring loaded intl loaded pdo_sqlite loaded sodium loaded openssl loaded dom loaded curl loaded
Recorded with PHP 8.5.11 (cli) (NTS) on macOS 26 arm64. To run it yourself: php env_report.php
In the recorded run every extension is loaded. On yours, a line may say MISSING. With the Windows zip,
most extensions are files in the ext folder that load only when an extension= line in php.ini names them, and
the manual notes that many such lines are already there behind a semicolon, which you remove. On Debian or Ubuntu,
an extension is a package of its own, such as php8.4-mbstring. Extensions that do not come with PHP are installed
with PIE, PHP’s installer for extensions.
Checkpoint
Check: Reading php -v and php -m
Find the settings file PHP reads
The report above printed php.ini: none loaded, because the recorded runs in this track use PHP’s built-in
settings, with no php.ini: PHP's main settings file, read when PHP starts. php --ini shows which php.ini was loaded, if any, and which extra .ini files were read from the scan folder.. php --ini shows the same thing from the terminal, and PHP 8.5
added --ini=diff, which lists every setting that differs from its built-in default:
# The commands of this session, as you would type them in a terminal; the output shows what they printed.
from session import run
run("""
php --ini
php --ini=diff
""") Output
$ php --ini Configuration File (php.ini) Path: "…/php/8.5.11" Loaded Configuration File: (none) Scan for additional .ini files in: "…/php/8.5.11/conf.d" Additional .ini files parsed: …/php/8.5.11/conf.d/php.ini $ php --ini=diff Non-default INI settings: html_errors: "1" -> "0" implicit_flush: "0" -> "1" max_execution_time: "30" -> "0"
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 ini_session.py
The four lines of php --ini answer four questions. The first is the folder where this build looks for its php.ini;
… stands for the folder this PHP is installed in, and on your computer it is a full path, such as etc/php/8.5
inside the Homebrew folder. The second line says no php.ini was loaded. The third and fourth name the scan folder,
conf.d, whose .ini files PHP reads after php.ini; this build has one file there, and since --ini=diff shows
nothing from it, it sets nothing. The three lines that --ini=diff did print come from the command line itself: the
manual lists them among the CLI’s own overrides, no HTML in error messages, output written at once and no time limit
for scripts.
Each INI directive: One PHP setting, such as display_errors or date.timezone. Each has a built-in default that php.ini, the -d option of the php command or ini_set() can change. can come from several places, and PHP checks them in a fixed order. Step through it:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- One php.ini, the first found. When the php command starts, it looks for one php.ini in a fixed order, and the first place that has one wins. First comes -c, which names a file or a folder for this run.
- Found through -c. php -c dev.ini --ini loaded dev.ini in the session further down. PHP skips the other places and goes on to the scan folder.
- The PHPRC variable. Without -c, the PHPRC environment variable may name a folder to look in. On Windows the registry is checked next. The command line never looks in the current folder; other SAPIs do.
- The folder the build names. Then comes the folder compiled into the build, which php --ini prints as the Configuration File (php.ini) Path, and a php.ini there is the one PHP reads. On Windows the folder of php.exe is checked just before it.
- Nothing found. When no place has a php.ini, PHP runs with its built-in defaults. The PHP of the recorded runs reports Loaded Configuration File (none).
- The scan folder. Either way, PHP then reads every .ini file in its scan folder, in alphabetical order, and a later value replaces an earlier one. The official Docker image tells you to put your own settings there.
- -d wins. Last come -d options, such as php -d display_errors=0 script.php. Each sets one directive for this run only, over every file.
- A name for each SAPI. In each place, a file named for the SAPI, such as php-cli.ini for the command line, is used instead of php.ini. So the CLI and a web server can read different files from one folder.
When PHP reads the file matters as much as which file it reads. The manual says the command line reads php.ini on every run, while a server module reads it only once, when the web server starts. PHP-FPM is a long-running service as well, and keeps the settings it read at its start until it is restarted or reloaded. That is why an edit shows up in the terminal at once and on a website only after a restart.
Editing a file PHP never reads
php.ini-development and php.ini-production are templates: PHP reads neither until one is copied to the name and
folder it looks for. And the php.ini that php --ini reports is the command line’s; PHP-FPM often reads another
one. Before you edit, ask the PHP you care about which file it loaded.
Checkpoint
Check: Which php.ini PHP reads
A development php.ini shows every error and logs it
PHP comes with two php.ini templates. They differ mainly in who sees an error, which is what you change when you move from your own computer to a live site:
| Directive | php.ini-development | php.ini-production |
|---|---|---|
display_errors |
On | Off |
display_startup_errors |
On | Off |
error_reporting |
E_ALL |
E_ALL & ~E_DEPRECATED |
log_errors |
On | On |
zend.exception_ignore_args |
Off | On |
zend.assertions |
1 | -1 |
Development shows every error, keeps the arguments of each call in exception traces and runs assertions. Production
hides errors from visitors, leaves deprecation notices out, drops arguments from traces, where they could hold
passwords, and does not compile assertions at all. Both log errors, but neither says where: neither template sets
error_log, so you set it.
For your own computer, edit the php.ini that php --ini reported, or, when it reported none, copy
php.ini-development to php.ini in the folder it named. Then set two directives in it: date.timezone, the zone
your dates should use, such as Asia/Kolkata or Europe/London, because the built-in default is UTC; and
error_log, the full path of a log file. This shorter file holds the settings that matter most, and the session
below tries it with -c, which loads a file for one run without touching your real php.ini:
; Development settings: show every problem, and log it as well.
error_reporting = E_ALL
display_errors = On
display_startup_errors = On
log_errors = On
error_log = php-errors.log
date.timezone = Asia/Kolkata <?php
echo "Hello, $name\n"; # The commands of this session, as you would type them in a terminal; the output shows what they printed.
from session import run
run("""
php -i | grep -E '^(display_errors|log_errors|error_log|date.timezone) '
php -c dev.ini --ini
php -c dev.ini --ini=diff
php -c dev.ini warn.php
cat php-errors.log
""") Output
$ php -i | grep -E '^(display_errors|log_errors|error_log|date.timezone) ' display_errors => STDOUT => STDOUT error_log => no value => no value log_errors => Off => Off date.timezone => UTC => UTC $ php -c dev.ini --ini Configuration File (php.ini) Path: "…/php/8.5.11" Loaded Configuration File: "dev.ini" Scan for additional .ini files in: "…/php/8.5.11/conf.d" Additional .ini files parsed: …/php/8.5.11/conf.d/php.ini $ php -c dev.ini --ini=diff Non-default INI settings: date.timezone: "UTC" -> "Asia/Kolkata" error_log: (none) -> "php-errors.log" error_reporting: (none) -> "30719" html_errors: "1" -> "0" implicit_flush: "0" -> "1" log_errors: "0" -> "1" max_execution_time: "30" -> "0" $ php -c dev.ini warn.php Warning: Undefined variable $name in warn.php on line 3 Hello, $ cat php-errors.log […] PHP Warning: Undefined variable $name in warn.php on line 3
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 devini_session.py
php -i printed the built-in values first: errors shown, nothing logged, no log file, UTC. With -c dev.ini, the
second command confirms which file was loaded, and --ini=diff lists what the file changed, beside the command
line’s three overrides from before. error_reporting became 30719, the value of E_ALL since PHP 8.4. Then the
warning appeared twice: on the screen, from display_errors, and in php-errors.log, from log_errors, with a time
stamp that […] stands for and the word PHP in front. In a real php.ini, give error_log a full path, because a
relative one depends on the folder PHP was started in.
Key idea
On your own computer, show every error and log it as well; on a live site, log every error and show none, and restart the server after each change to php.ini.
Checkpoint
Check: A development php.ini
Practise: installing and checking PHP
This practice covers choosing an install, reading php -v and php -m, finding the php.ini and a development
setup. Your weakest topic comes first, and every answer shows why it is right or what it misses:
Practice that adapts to you
Practise this lesson
12 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher · Warm-up
What is the difference between php.ini-development and php.ini-production, and which one belongs on a live site?
Both are templates that come with PHP; neither is read until you copy it to the php.ini path that php --ini shows. php.ini-development shows every problem: it sets display_errors and display_startup_errors on, reports every error level with E_ALL, keeps the arguments of functions in exception traces and runs assertions. That is what you want on your own computer. php.ini-production hides errors from visitors: display_errors and display_startup_errors are off, errors are logged instead, deprecation notices are not reported, exception traces leave out arguments and assertions are not compiled at all. A live site uses the production file, with error_log pointing at a file you read, because an error message shown to visitors can reveal paths, queries and settings.
-
Fresher to senior
On a Linux server, php -v reports PHP 8.4, but a page that prints PHP_VERSION shows 8.2, and code that needs 8.4 fails on the site. Why can the two differ, and how do you find and fix the PHP that serves the site?
-
Fresher to senior
You set memory_limit = 512M in php.ini. php -r 'echo ini_get("memory_limit");' now prints 512M, but a report on the website still stops with "Allowed memory size of 134217728 bytes exhausted". Walk through how you work out why, and how you make the change take effect.
Key takeaways
- Choose an install by who keeps it patched and which branch it gives you: the php.net zip on Windows, Homebrew on macOS, the distribution’s packages on Linux, and a container image when the branch must match a server.
php -vnames the version, the SAPI and the build type, andphp -mlists the loaded extensions; on Windows an extension loads only when anextension=line in the php.ini PHP reads names it.php --inishows the php.ini that was loaded and the scan folder, andphp --ini=diffshows what differs from the built-in defaults; -c, PHPRC and the compiled-in folder are checked in that order, and -d wins over every file.- The command line reads php.ini on every run, while web servers and PHP-FPM read it when they start, so restart them after an edit.
- A development php.ini shows and logs every error and sets
date.timezoneanderror_log; a live site uses the production template, which logs errors and shows none.
Terms in this lesson
- extension
- A module that adds functions and classes to PHP, such as mbstring, intl, pdo_sqlite or sodium. php -m lists the extensions an installation has loaded.
- INI directive
- One PHP setting, such as display_errors or date.timezone. Each has a built-in default that php.ini, the -d option of the php command or ini_set() can change.
- NTS build
- A Non Thread Safe build of PHP, the usual build for the command line and PHP-FPM. A Thread Safe (TS or ZTS) build is needed when PHP runs as a module inside a threaded web server.
- php.ini
- PHP's main settings file, read when PHP starts. php --ini shows which php.ini was loaded, if any, and which extra .ini files were read from the scan folder.
References
- PHP Manual: Installation and configuration (The PHP Group (php.net))
- PHP downloads for Windows (The PHP Group (php.net))
- PHP Manual: Running PHP on the command line on Windows (The PHP Group (php.net))
- PHP Manual: Installing a PHP extension on Windows (The PHP Group (php.net))
- PHP Manual: Installing from packages on Debian GNU/Linux (The PHP Group (php.net))
- PHP Manual: The configuration file (The PHP Group (php.net))
- PHP Manual: Command line options (The PHP Group (php.net))
- PHP Manual: Differences to other SAPIs (command line) (The PHP Group (php.net))
- php.ini-development, PHP 8.5 branch (The PHP Group (php-src on GitHub))
- php.ini-production, PHP 8.5 branch (The PHP Group (php-src on GitHub))
- PHP Manual: Error handling runtime configuration (The PHP Group (php.net))
- PHP Manual: Date/Time runtime configuration (The PHP Group (php.net))
- PHP Manual: Introduction to PIE (The PHP Group (php.net))
- php formula (Homebrew)
- php package in Debian trixie (Debian)
- php packages in Ubuntu releases (Canonical (Ubuntu))
- php: Docker Official Image (Docker)
- Docker Official Image php, supported tags and usage (Docker Library (GitHub))
- XAMPP downloads (Apache Friends)
- XAMPP for Windows FAQ (Apache Friends)
Report a problem with this lesson
Kept only in this browser. Your Learn progress