Three Pieces, Not One
"Installing PHP" sounds like one job, but a working setup is really three separate things, and knowing which is which turns most installation problems into five-minute fixes.
The PHP interpreter is the program that reads your code and runs it. The web server — Apache or Nginx — is the program that listens for browser requests and decides which file should handle them; it is what makes http://localhost/index.php mean anything. The database server, usually MySQL or MariaDB, stores your data and is a completely independent program that PHP talks to over a connection.
You can have any one of them running without the others. That is why "my page shows a 404" (the web server cannot find the file), "my browser downloads the .php file instead of running it" (the web server is not passing it to PHP), and "Connection refused" (the database server is not running) are three different problems with three different fixes, even though they all look like "PHP is broken".
- PHP interpreter — runs your code; check it with
php -v - Web server — Apache or Nginx, listens on port 80 or 8000 and routes requests to PHP
- Database server — MySQL or MariaDB, listens on port 3306 and holds your tables
- Composer — the dependency manager you will need the moment you use somebody else's library
- An editor with PHP support — VS Code with the PHP Intelephense extension is the common free choice
- Windows, macOS and Linux all run the same PHP code. The differences you will meet are file paths (
C:\xampp\htdocsversus/var/www/html) and how services are started, not the language itself.
Option 1: XAMPP, the All-in-One Bundle
XAMPP installs Apache, MySQL (MariaDB), PHP and phpMyAdmin together in one package. For a first server-side project this is the path of least resistance, and it is what most Indian college labs already have installed. Download it from apachefriends.org, install it, open the XAMPP Control Panel, and press Start next to Apache and MySQL.
Your web files go in the htdocs folder — C:\xampp\htdocs on Windows, /Applications/XAMPP/htdocs on macOS. A file saved as htdocs/myproject/index.php is reachable in the browser at http://localhost/myproject/index.php. Note the shape of that address carefully: it starts with http://, not with a drive letter. If your address bar shows file:///C:/xampp/htdocs/..., you opened the file instead of requesting it, and PHP never ran.
The one failure almost everybody hits is Apache refusing to start. Apache wants port 80, and on Windows something else often already has it — IIS, a VPN client, or another development tool. The Control Panel log will say so plainly. The quickest fix is to move Apache to a different port by editing httpd.conf, changing Listen 80 to Listen 8080, and then visiting http://localhost:8080/ instead. MySQL failing to start on port 3306 usually means a separate MySQL service is already installed on the machine.
phpMyAdmin comes with XAMPP at http://localhost/phpmyadmin. It is a web interface for creating databases and tables and for running SQL by hand, which saves you from writing throwaway PHP scripts just to look at your data. The default local login is user root with an empty password — fine on your own laptop, and something you must never replicate on a real server.
# Where your files live (Windows XAMPP)
C:\xampp\htdocs\myproject\index.php
-> http://localhost/myproject/index.php
# Changing Apache's port when 80 is taken
# Edit C:\xampp\apache\conf\httpd.conf
# Listen 80 -> Listen 8080
# ServerName localhost:80 -> ServerName localhost:8080
# Restart Apache, then open http://localhost:8080/
# phpMyAdmin (bundled)
http://localhost/phpmyadmin - XAMPP is a development tool. It is deliberately convenient rather than secure — blank database password, all services exposed — so it is never the right way to run a public website. Real hosting gives you a properly configured server instead.
Option 2: PHP on Its Own, With the Built-in Server
PHP ships with a small web server of its own, which means you can skip Apache entirely while you are learning. Install PHP through your system's package manager, move into your project folder, and run one command. There is no configuration file, no service to start, and no port 80 conflict.
The -S flag takes the address to listen on and -t optionally sets the folder to serve. Requests are logged straight to your terminal, which is genuinely useful — you can see each request arrive, along with its status code, as you click around. Stop the server with Ctrl+C.
The important limitation: this server handles one request at a time and has none of the features a real site needs. It is documented as a development tool and nothing more. Use it to learn and to build, then deploy to Apache or Nginx. Also remember that it does not read .htaccess files at all, so URL rewriting rules you may have copied from a tutorial will simply be ignored here.
# Install
# Ubuntu / Debian
sudo apt install php-cli php-mysql php-mbstring php-curl
# macOS (Homebrew)
brew install php
# Windows: download the zip from windows.php.net, or just use XAMPP
# Serve the current folder on port 8000
php -S localhost:8000
# Serve a specific folder as the document root
php -S localhost:8000 -t public
# Run a single script with no server at all (useful for practice)
php script.php
# Check what you have
php -v # version
php -m # list of loaded extensions
php --ini # which php.ini file is actually being used php --iniis the command to reach for whenever a setting you changed seems to have no effect. PHP can have severalphp.inifiles on one machine — often a different one for the command line and for the web server — and editing the wrong one is a classic hour-waster.
php.ini: the Settings That Actually Bite
php.ini is a plain text file of name = value lines that controls how PHP behaves. You do not need to understand all of it, but a handful of settings cause a disproportionate share of beginner problems, and it is worth being able to recognise their symptoms.
display_errors decides whether error messages appear on the page. On your own machine set it to On, together with error_reporting = E_ALL, so PHP tells you everything. On a live site set it to Off and turn on log_errors instead, because an uncaught error message can reveal file paths, database names and sometimes credentials to whoever triggered it.
upload_max_filesize and post_max_size travel together and catch nearly everyone. The first limits a single uploaded file; the second limits the entire POST request, files included. If a visitor posts more than post_max_size, PHP discards the request body — and then both $_POST and $_FILES arrive completely empty, with no obvious error. A form that "just does nothing" when a large photo is attached is almost always this. Keep post_max_size comfortably larger than upload_max_filesize.
date.timezone is worth setting deliberately. If your project is for users in India, set it to Asia/Kolkata so that date() and any timestamps you record match what your users see on their own clocks. Leaving it at the default UTC is fine too, as long as you know that is what you chose.
; ---- development ----
display_errors = On
display_startup_errors = On
error_reporting = E_ALL
; ---- production ----
display_errors = Off
log_errors = On
error_log = /var/log/php/error.log
; ---- limits ----
memory_limit = 128M
max_execution_time = 30 ; seconds; 0 (unlimited) on the command line
upload_max_filesize = 8M
post_max_size = 12M ; must be larger than upload_max_filesize
; ---- locale ----
date.timezone = Asia/Kolkata
; ---- extensions (remove the leading ; to enable) ----
extension=pdo_mysql
extension=mysqli
extension=mbstring
extension=fileinfo
extension=curl
extension=gd - After editing
php.iniyou must restart the web server, or the old values stay in memory. With XAMPP that means Stop then Start on Apache — not just saving the file.
Changing Settings From Code, and When You Cannot
Some settings can be changed at runtime with ini_set(), which is handy when you do not control php.ini — a common situation on cheap shared hosting. Error display, memory limit and execution time can all be adjusted this way from the top of a script.
Others cannot, and understanding why saves you from a frustrating loop. Settings such as upload_max_filesize and post_max_size are read before your script runs, because PHP has to know the limits while it is still receiving the request body. By the time your first line executes, the decision has already been made, so calling ini_set('upload_max_filesize', '20M') silently does nothing at all. Those must be changed in php.ini, or in a .htaccess file if your host allows it.
phpinfo() is your reference for all of this. Put it in a throwaway file, load it in the browser, and you get the full list of active settings, loaded extensions, and the exact php.ini path in use. Delete that file when you are done — on a public server it is an information gift to anyone probing your site.
<?php
// Useful at the top of a development-only bootstrap file
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
// Sometimes needed for a heavy import script
ini_set('memory_limit', '256M');
set_time_limit(120); // 0 means no limit
// Read a value rather than guess at it
echo ini_get('upload_max_filesize'); // e.g. "8M"
echo date_default_timezone_get(); // e.g. "Asia/Kolkata"
// Full configuration report - delete this file afterwards
// phpinfo(); - Keep a single
config.phpthat you include at the top of every page, and put your environment switch there — errors on your laptop, errors logged and hidden on the live site. One file to change when you deploy is far safer than remembering to edit twenty.
Composer: Getting Other People's Code
Composer is PHP's dependency manager. When you need to send email, generate a PDF, read an Excel file or talk to a payment gateway, you do not write that yourself — you install a library. Composer downloads it, downloads whatever it depends on, and writes a single file that loads all of them automatically.
Two files describe your project. composer.json lists what you asked for, and composer.lock records the exact versions that were installed, so that a teammate running composer install gets byte-for-byte the same code as you. Commit both to git; never commit the vendor/ folder, which is just downloaded code and can always be regenerated.
The payoff is a single line at the top of your project: require __DIR__ . '/vendor/autoload.php';. After that, every class from every installed library is available by name, and — if you configure the autoload section — so are your own classes, without a long list of require statements at the top of each file. The class-loading lesson later in this course explains how that works.
# Install Composer from getcomposer.org, then:
composer --version
# Start a new project
composer init
# Add a library (this one loads .env config files)
composer require vlucas/phpdotenv
# On another machine, install exactly what composer.lock records
composer install
# In your PHP code, one line makes everything available
# require __DIR__ . '/vendor/autoload.php'; - Add
vendor/to your.gitignore. It is downloaded code, it is often larger than your entire project, andcomposer installreproduces it exactly fromcomposer.lock.
