Adding PHP

Chapter 4 — PHP

PHP is the glue between your web server and your database. This chapter covers two installation paths — the version that ships with Debian 12 Bookworm's main repository (PHP 8.2) and the newer PHP 8.3 available via the Sury third-party repository — along with the extensions you'll need for a real-world site, the key configuration settings to change from their defaults, and how to verify everything is working for both Apache and Nginx.

Which version? PHP 8.2 (Debian main repo) is stable, well-supported, and the simplest to install — one command, no extra repository. PHP 8.3 adds typed class constants, readonly properties in cloned objects, and minor performance gains. For Philip's Learning Blog, 8.2 is the recommended choice; upgrading to 8.3 later is straightforward via the Sury repo.

Option A — PHP 8.2 (Debian Main Repository)

Installation

PHP 8.2 is available directly from Debian's main repository — no GPG keys or third-party sources to configure. The package you need depends on your web server.

◆ PHP 8.2 · Debian Main
For Apache (mod_php)
philip@debian — PHP 8.2 install (Apache)
philip@debian:~$ sudo apt install -y php php-cli php-mysql php-mbstring php-curl \ php-gd php-xml php-zip php-intl php-bcmath Reading package lists... Done The following NEW packages will be installed: php php-cli php-common php-mysql php-mbstring ... (and dependencies) Setting up php8.2 (8.2.x-1+deb12uN) ... Setting up libapache2-mod-php8.2 (8.2.x-1+deb12uN) ... philip@debian:~$ sudo systemctl restart apache2
For Nginx (PHP-FPM)
philip@debian — PHP 8.2 install (Nginx + FPM)
philip@debian:~$ sudo apt install -y php-fpm php-cli php-mysql php-mbstring php-curl \ php-gd php-xml php-zip php-intl php-bcmath Setting up php8.2-fpm (8.2.x-1+deb12uN) ... philip@debian:~$ sudo systemctl enable --now php8.2-fpm philip@debian:~$ sudo systemctl restart nginx

Option B — PHP 8.3 (Sury Repository)

The deb.sury.org repository is maintained by Ondřej Surý, a Debian Developer who maintains the official PHP packages. It is widely used in production and is the standard way to get PHP versions newer than what Debian ships.

◆ PHP 8.3 · Sury Repository
philip@debian — PHP 8.3 via Sury
philip@debian:~$ sudo apt install -y apt-transport-https lsb-release ca-certificates curl philip@debian:~$ curl -sSLo /tmp/debsuryorg-archive-keyring.deb \ https://packages.sury.org/php/debsuryorg-archive-keyring.deb philip@debian:~$ sudo dpkg -i /tmp/debsuryorg-archive-keyring.deb philip@debian:~$ echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" \ | sudo tee /etc/apt/sources.list.d/php.list deb https://packages.sury.org/php/ bookworm main philip@debian:~$ sudo apt update philip@debian:~$ sudo apt install -y php8.3 php8.3-cli php8.3-fpm php8.3-mysql \ php8.3-mbstring php8.3-curl php8.3-gd php8.3-xml php8.3-zip \ php8.3-intl php8.3-bcmath Setting up php8.3-fpm (8.3.x+1) ... philip@debian:~$ sudo update-alternatives --set php /usr/bin/php8.3
Version pinning: if both 8.2 and 8.3 packages are installed, update-alternatives --set php /usr/bin/php8.3 sets which binary php calls on the command line. The web server module (mod_php or FPM) is controlled separately — make sure Apache/Nginx points at the FPM socket for your chosen version.

Essential Extensions

A bare PHP install will process scripts, but you'll need extensions for database access, image handling, and character encoding. Below are the extensions used by Philip's Learning Blog and other common PHP sites.

php-mysql Essential
PDO MySQL driver and MySQLi — required for any MySQL / MariaDB connection.
php-mbstring Essential
Multi-byte string functions — required by most frameworks. Without it, Unicode text handling breaks.
php-curl Essential
HTTP requests from PHP — needed for any external API calls (Anthropic, payment gateways, etc.).
php-xml Essential
XML/DOM parsing — required by Composer, PHPUnit, and many libraries.
php-gd Useful
Image creation and manipulation — resize thumbnails, add watermarks, generate CAPTCHA images.
php-zip Useful
Read/write ZIP archives — required by Composer to install packages from GitHub.
php-intl Useful
Internationalisation — locale-aware number/date formatting and transliteration.
php-bcmath Useful
Arbitrary precision maths — avoids floating-point rounding errors in financial calculations.
php-pgsql PostgreSQL
PDO PostgreSQL driver — only needed if using PostgreSQL instead of MySQL/MariaDB.
php-redis Optional
Redis client — session storage, caching. Install Redis server separately first.
php-imagick Optional
ImageMagick bindings — more powerful than GD for image conversion and PDF thumbnails.
php-opcache Performance
Caches compiled PHP bytecode in memory — enabled by default in Debian packages, speeds up every request.
Checking installed extensions: run php -m to list all loaded modules, or php -m | grep -i mysql to confirm a specific one. The output from phpinfo() in a browser shows the full picture including configuration values.

Configuration — php.ini

PHP uses separate php.ini files for CLI and for each SAPI (Apache mod_php and PHP-FPM have their own). The paths depend on the PHP version.

Context php.ini path (PHP 8.2) php.ini path (PHP 8.3)
Apache (mod_php) /etc/php/8.2/apache2/php.ini /etc/php/8.3/apache2/php.ini
PHP-FPM (Nginx) /etc/php/8.2/fpm/php.ini /etc/php/8.3/fpm/php.ini
CLI (command line) /etc/php/8.2/cli/php.ini /etc/php/8.3/cli/php.ini

Recommended Changes

Open the appropriate php.ini for your web server SAPI and adjust these values. The defaults are conservative — fine for shared hosting, too restrictive for a real development machine.

philip@debian — edit php.ini (Apache example)
philip@debian:~$ sudo vi /etc/php/8.2/apache2/php.ini
Setting Default Recommended Why
memory_limit 128M 256M Enough headroom for image processing and Composer installs.
upload_max_filesize 2M 20M Allows uploading images and documents without hitting the limit.
post_max_size 8M 25M Must be larger than upload_max_filesize — controls total POST body.
max_execution_time 30 60 Prevents slow scripts from hanging the server indefinitely.
display_errors Off On (dev only) Show errors in the browser during development. Set to Off on production.
error_reporting E_ALL & ~E_DEPRECATED E_ALL Catch everything including deprecation notices during development.
date.timezone (empty) Europe/London Avoids "It is not safe to rely on the system's timezone" warnings.
Production warning: display_errors = On leaks internal paths and logic to the browser. Always set it to Off on a publicly accessible server, and log errors to a file instead using log_errors = On and error_log = /var/log/php_errors.log.

Connecting PHP to Apache

When you install php (or libapache2-mod-php8.2), the module is enabled automatically. You can verify this and ensure the right module is active:

philip@debian — Apache + mod_php
philip@debian:~$ apache2ctl -M | grep php php8.2_module (shared) philip@debian:~$ sudo systemctl restart apache2

No virtual host changes are needed — Apache handles .php files automatically via mod_php as long as the module is loaded.

Connecting PHP to Nginx (PHP-FPM)

Nginx does not embed a PHP interpreter. Instead it passes .php requests to a separate PHP-FPM (FastCGI Process Manager) process via a Unix socket. Your Nginx server block needs a location ~ \.php$ block pointing at that socket.

Verify the FPM socket path

philip@debian — find FPM socket
philip@debian:~$ ls /run/php/ php8.2-fpm.pid php8.2-fpm.sock

Update the Nginx server block

Add or confirm the PHP location block inside your server {} block. This is the block introduced in Chapter 2 for the Nginx virtual host.

# /etc/nginx/sites-available/osztromok.com server { listen 80; server_name osztromok.com www.osztromok.com; root /var/www/osztromok.com/public_html; index index.php index.html; location / { try_files $uri $uri/ =404; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.2-fpm.sock; } location ~ /\.ht { deny all; } }
philip@debian — reload Nginx
philip@debian:~$ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful philip@debian:~$ sudo systemctl reload nginx

Testing the PHP Installation

Step 1 — phpinfo() test page

Create a temporary test file in your web root to confirm PHP is executing:

philip@debian — create test page
philip@debian:~$ echo '<?php phpinfo(); ?>' | sudo tee /var/www/osztromok.com/public_html/info.php

Visit http://<your-server-ip>/info.php in a browser. You should see the purple PHP information page listing your version, loaded extensions, and all configuration values.

Remove it immediately after testing. The phpinfo() page exposes your server configuration to anyone who can reach it. sudo rm /var/www/osztromok.com/public_html/info.php

Step 2 — Command-line version check

philip@debian — CLI checks
philip@debian:~$ php --version PHP 8.2.20 (cli) (built: Jun 4 2025 07:35:12) (NTS) Copyright (c) The PHP Group philip@debian:~$ php -m | grep -E 'mysql|mbstring|curl|gd|xml|zip' curl gd mbstring mysqlnd pdo_mysql SimpleXML xml zip

Step 3 — PHP-to-database connection test

This quick script confirms PHP can actually open a PDO connection to your MariaDB / MySQL database. Run it from the CLI — no browser needed.

# /tmp/db_test.php — delete after testing <?php $dsn = 'mysql:host=localhost;dbname=learning_blog;charset=utf8mb4'; $user = 'philip'; $pass = 'AsT@1sAd3mon'; try { $pdo = new PDO($dsn, $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]); $ver = $pdo->query('SELECT VERSION()')->fetchColumn(); echo "Connected OK — database version: $ver\n"; } catch (PDOException $e) { echo "Connection failed: " . $e->getMessage() . "\n"; } ?>
philip@debian — run connection test
philip@debian:~$ php /tmp/db_test.php Connected OK — database version: 10.11.6-MariaDB-2 philip@debian:~$ rm /tmp/db_test.php

Installing Composer

Composer is PHP's dependency manager — the equivalent of npm for Node. It's not strictly required to run Philip's existing site, but it's needed for any modern PHP framework (Laravel, Symfony, FastAPI-style microframeworks like Slim) or if you install libraries via require statements.

philip@debian — install Composer globally
philip@debian:~$ curl -sS https://getcomposer.org/installer | php All settings correct for using Composer Downloading... Composer (version 2.x.x) successfully installed to: /home/philip/composer.phar philip@debian:~$ sudo mv composer.phar /usr/local/bin/composer philip@debian:~$ composer --version Composer version 2.x.x 2025-xx-xx xx:xx:xx

PHP Management Commands

Task Apache (mod_php) Nginx (PHP-FPM)
Restart PHP after config change sudo systemctl restart apache2 sudo systemctl restart php8.2-fpm
Check PHP-FPM status apache2ctl -M | grep php sudo systemctl status php8.2-fpm
View FPM error log sudo tail -f /var/log/php8.2-fpm.log
List installed extensions php -m php -m
Install a new extension sudo apt install php-<name> sudo apt install php8.2-<name>
Confirm active php.ini php --ini php --ini
OPcache status php -r "echo opcache_get_status()['opcache_enabled'] ? 'enabled' : 'disabled';"

Quick-Verification Checklist

  • PHP version matches expectationphp --version shows 8.2.x or 8.3.x.
  • Browser test page worksinfo.php returns the PHP purple page, then removed.
  • All required extensions loadedphp -m shows mysql, mbstring, curl, gd, xml, zip.
  • Database connection succeedsdb_test.php prints "Connected OK", then removed.
  • OPcache enabled — improves every page load with zero code changes.
  • php.ini tuned for environment — memory, upload limits, timezone set, display_errors correct for dev/prod.
  • FPM socket active (Nginx only)/run/php/php8.2-fpm.sock exists, Nginx reload clean.
Next — Chapter 5: SSL / HTTPS. With PHP serving dynamic pages, the next step is encrypting traffic. Chapter 5 covers obtaining a free Let's Encrypt certificate with Certbot (for public-facing domains) and generating a self-signed certificate (for local development), with automatic HTTP → HTTPS redirect for both Apache and Nginx.