🔧 Troubleshooting Guide
Step-by-step solutions for the most common issues you'll encounter while working through this course — from LAMP stack setup to PDO database connections.
📑 Sections
PHP Installation & Setup
🔹 PHP Not Installed or Wrong Version
Symptom: php: command not found or you need PHP 8.x but have an older version.
# Check your PHP version
php -v
# If not installed, install PHP 8.x with common extensions
sudo apt update
sudo apt install php php-cli php-common php-mysql php-mbstring php-xml php-curl
# Check installed modules
php -m
# If you need a specific version (e.g., 8.3)
sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install php8.3
🔹 PHP Works in Terminal but Not in Browser
Symptom: php -v works, but .php files in the browser show raw code or download instead of executing.
Cause: The Apache PHP module isn't installed or enabled.
# Install the Apache PHP module
sudo apt install libapache2-mod-php
# Enable it
sudo a2enmod php8.3 # Use your PHP version number
# Restart Apache
sudo service apache2 restart
# Test: create a PHP info file
echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/info.php
# Visit http://localhost/info.php — you should see a purple info page
🔹 Missing PHP Extension
Symptom: Call to undefined function or Class 'PDO' not found.
# Check which extensions are loaded
php -m
# Install common missing extensions
sudo apt install php-mysql # PDO MySQL driver
sudo apt install php-mbstring # Multibyte string support
sudo apt install php-xml # XML/DOM support
sudo apt install php-curl # cURL support
sudo apt install php-gd # Image processing
# Restart Apache after installing extensions
sudo service apache2 restart
🔹 php.ini Changes Not Taking Effect
Cause: PHP uses different php.ini files for CLI and Apache. You may be editing the wrong one.
# Find which php.ini Apache is using
php -i | grep "Loaded Configuration File"
# The CLI version:
# /etc/php/8.3/cli/php.ini
# The Apache version (the one you usually want):
# /etc/php/8.3/apache2/php.ini
# After editing, ALWAYS restart Apache
sudo service apache2 restart
Apache & Web Server Issues
🔹 Apache Won't Start
Symptom: sudo service apache2 start fails.
# Check error details
sudo service apache2 status
sudo apache2ctl configtest
# Common cause: port 80 already in use
sudo lsof -i :80
# If another process is using port 80, stop it or change Apache's port
# Edit /etc/apache2/ports.conf → change "Listen 80" to "Listen 8080"
sudo service apache2 restart
🔹 403 Forbidden Error
Symptom: Browser shows "Forbidden — You don't have permission to access this resource."
# Check file permissions
ls -la /var/www/html/
# Fix permissions
sudo chmod 644 /var/www/html/*.php
sudo chmod 755 /var/www/html/
# Check Apache config for Directory permissions
sudo nano /etc/apache2/apache2.conf
# Ensure the <Directory /var/www/> block has:
# Options Indexes FollowSymLinks
# AllowOverride None
# Require all granted
🔹 404 Not Found for PHP Files
Possible Causes:
- File is not in the web root (
/var/www/html/) - Filename has a typo or wrong case (Linux is case-sensitive!)
- Missing
.phpextension in the URL
# Verify the file exists where you think it does
ls -la /var/www/html/your_file.php
# Check the document root in Apache config
grep -r "DocumentRoot" /etc/apache2/sites-enabled/
🔹 500 Internal Server Error
Symptom: Blank page or "Internal Server Error" with no details.
# Check the Apache error log — this is your best friend
sudo tail -20 /var/log/apache2/error.log
# Common causes:
# - PHP parse error (syntax mistake)
# - Missing file in require/include
# - Permission denied on a file
# - .htaccess misconfiguration
PHP Syntax & Runtime Errors
🔹 Blank White Page (White Screen of Death)
Symptom: The page loads but is completely blank — no output, no error.
Cause: PHP has a fatal error but error display is turned off.
// Add this at the TOP of your PHP file during development:
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// Or enable in php.ini (Apache version):
// display_errors = On
// error_reporting = E_ALL
🔹 "Unexpected T_STRING" or Parse Errors
Common Causes:
- Missing semicolon
;at end of a statement - Unclosed string (missing quote)
- Unclosed parenthesis, bracket, or brace
- Using a reserved word as a variable name
// Missing semicolon — error points to the NEXT line
$name = "Ray" // ← missing ;
echo $name; // ← error reported here
// Fix:
$name = "Ray";
echo $name;
// Tip: The error line number is often ONE LINE AFTER the actual mistake
🔹 "Undefined Variable" Warning
Cause: Using a variable before assigning it, or a typo in the variable name.
// Bad — $naem is a typo
$name = "Ray";
echo $naem; // Warning: Undefined variable $naem
// Fix: Check your spelling!
// Also common with form data:
// If the form hasn't been submitted yet, $_POST['name'] doesn't exist
$name = $_POST['name'] ?? ''; // Use null coalescing to provide a default
🔹 "Cannot Modify Header Information — Headers Already Sent"
Cause: You called header(), session_start(), or setcookie() after output was already sent to the browser.
// Bad — HTML output before header()
<html>
<?php
header("Location: dashboard.php"); // FAILS — HTML was already sent
?>
// Fix — PHP logic BEFORE any output
<?php
header("Location: dashboard.php");
exit;
?>
<html>
...
// Also watch for:
// - Whitespace BEFORE the opening <?php tag
// - A BOM (byte order mark) in the file — save as UTF-8 without BOM
// - echo or print statements before header calls
🔹 "Allowed Memory Size Exhausted"
Cause: Script uses more memory than PHP allows (usually 128 MB).
// Quick fix in your script:
ini_set('memory_limit', '256M');
// Better: find the cause — usually:
// - Loading a huge file into memory at once
// - Infinite loop creating objects
// - Fetching too many database rows at once
// Permanent fix in php.ini:
// memory_limit = 256M
Form Handling Problems
🔹 $_POST Is Empty After Form Submission
Common Causes:
- Form
methodisGETbut you're reading$_POST - Form fields are missing the
nameattribute - Form
actionpoints to the wrong file
// Checklist:
// 1. Form has method="post"
// <form method="post" action="process.php">
// 2. Every input has a name attribute
// <input type="text" name="username"> ← "name" is required!
// 3. Debug what's actually arriving:
echo "<pre>";
print_r($_POST);
echo "</pre>";
// 4. For file uploads, don't forget enctype:
// <form method="post" enctype="multipart/form-data">
🔹 Form Data Contains HTML/Script Tags (XSS Risk)
Symptom: User input like <script>alert('hacked')</script> actually executes in the page.
// ALWAYS escape output with htmlspecialchars()
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// For form values (sticky forms):
<input type="text" name="name"
value="<?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>">
// See Lesson 12 (validation) and Lesson 23 (security) for full coverage
🔹 Checkbox / Multi-Select Values Missing
Cause: Checkboxes only send a value when checked. Multi-select inputs need name="items[]" (with brackets).
// Checkbox — only present in $_POST when checked
$agreed = isset($_POST['agree']); // true/false
// Multi-select or multiple checkboxes — use array notation
// <select name="colors[]" multiple>
// <input type="checkbox" name="hobbies[]" value="coding">
$colors = $_POST['colors'] ?? []; // Array of selected values
Session & Cookie Issues
🔹 session_start() Gives "Headers Already Sent" Error
Fix: session_start() must be called before ANY output — including whitespace, HTML, or echo statements.
// This must be the VERY FIRST thing in your PHP file:
<?php
session_start();
// ... rest of your code
?>
<!DOCTYPE html>
...
// Tip: No blank lines or spaces before <?php
🔹 Session Data Lost Between Pages
Common Causes:
- Forgot to call
session_start()on the page that reads the session - Session cookie is being blocked (check browser settings)
- Mixing
http://andhttps://orlocalhostand127.0.0.1
// EVERY page that uses $_SESSION needs session_start() at the top
<?php
session_start();
// Debug: check the session ID — it should be the same across pages
echo session_id();
// Debug: dump all session data
echo "<pre>";
print_r($_SESSION);
echo "</pre>";
🔹 Cookies Not Being Set
Common Causes:
setcookie()called after output (same "headers already sent" issue)- Cookie set for wrong path or domain
- Cookie has already expired (check the expiry time)
// setcookie() must be called before ANY output
<?php
setcookie("username", "Ray", time() + 3600, "/"); // Expires in 1 hour, available site-wide
?>
// The cookie won't be in $_COOKIE until the NEXT request
// (the browser sends it back on subsequent page loads)
// Debug: check what cookies the browser has
echo "<pre>";
print_r($_COOKIE);
echo "</pre>";
File Handling Issues
🔹 "Failed to Open Stream: Permission Denied"
Cause: PHP (running as www-data) doesn't have permission to read/write the file.
# Check ownership and permissions
ls -la /var/www/html/data/
# Give Apache's user (www-data) write access
sudo chown www-data:www-data /var/www/html/data/
sudo chmod 755 /var/www/html/data/
# For individual files:
sudo chown www-data:www-data /var/www/html/data/log.txt
sudo chmod 644 /var/www/html/data/log.txt
🔹 include/require File Not Found
Cause: The path is relative to the currently executing script, not the included file.
// Bad — relative path depends on which file is being executed
include 'header.php';
// Better — use __DIR__ for reliable paths
include __DIR__ . '/includes/header.php';
// __DIR__ = the directory of the CURRENT file, not the calling script
// require vs include:
// require → fatal error if file not found (page stops)
// include → warning if file not found (page continues)
// Use require for essential files (config, functions)
PDO & Database Connection Problems
🔹 "Could Not Find Driver" or "Class 'PDO' Not Found"
Cause: The PDO MySQL driver extension isn't installed.
# Install the PDO MySQL driver
sudo apt install php-mysql
# Verify it's loaded
php -m | grep pdo
# You should see:
# pdo_mysql
# PDO
# Restart Apache
sudo service apache2 restart
🔹 "Connection Refused" or "No Such File or Directory"
Cause: MySQL isn't running, or the connection details are wrong.
# 1. Check if MySQL is running
sudo service mysql status
# 2. Start it if it's not
sudo service mysql start
# 3. Verify you can connect from the command line
mysql -u root -p
// Double-check your DSN:
$dsn = "mysql:host=localhost;dbname=your_database;charset=utf8mb4";
// Common mistakes:
// - Wrong database name (case-sensitive!)
// - Using 127.0.0.1 instead of localhost (or vice versa)
// - Wrong port (default is 3306)
// - Database doesn't exist yet — create it first in MySQL
🔹 "Access Denied for User"
Cause: Wrong username/password, or the user doesn't have access to that database.
# Check which users exist and their authentication method
sudo mysql -e "SELECT User, Host, plugin FROM mysql.user;"
# If root uses auth_socket, create a dedicated PHP user:
sudo mysql
CREATE USER 'php_user'@'localhost' IDENTIFIED BY 'StrongPassword!';
GRANT ALL PRIVILEGES ON your_database.* TO 'php_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
// Then use the new user in your PHP connection:
$pdo = new PDO(
"mysql:host=localhost;dbname=your_database;charset=utf8mb4",
"php_user",
"StrongPassword!",
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
🔹 Prepared Statement Errors
Symptom: Queries run without errors but return no results, or you get unexpected behavior.
// Mistake 1: Using table/column names as parameters (not allowed)
// Bad:
$stmt = $pdo->prepare("SELECT * FROM ?"); // WRONG
$stmt = $pdo->prepare("SELECT * FROM :table"); // WRONG
// Placeholders are for VALUES only, not identifiers
// Mistake 2: Forgetting to execute
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->bindValue(1, $id);
// $stmt->execute(); ← Don't forget this!
$results = $stmt->fetchAll(); // Empty without execute()
// Mistake 3: Mismatched placeholder count
$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
$stmt->execute([$name]); // Only 1 value for 2 placeholders → error
// Always enable exceptions to catch PDO errors:
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
OOP Issues
🔹 "Using $this When Not in Object Context"
Cause: You're using $this inside a static method, or calling an instance method as if it were static.
class User {
public $name;
// Static methods cannot use $this
public static function create() {
// $this->name = "Ray"; // WRONG — no $this in static context
return new self(); // Correct — create a new instance
}
public function greet() {
echo "Hello, " . $this->name; // Fine — instance method
}
}
// Must create an instance first:
$user = new User();
$user->greet();
// Not:
// User::greet(); // WRONG — greet() is not static
🔹 "Cannot Access Private/Protected Property"
Cause: Trying to access a private or protected property from outside the class.
class BankAccount {
private float $balance = 0;
public function getBalance(): float {
return $this->balance; // Access via a public method
}
}
$account = new BankAccount();
// echo $account->balance; // FATAL ERROR — private!
echo $account->getBalance(); // Correct — use the getter
File Upload Problems
🔹 $_FILES Is Empty After Upload
Common Causes:
- Form is missing
enctype="multipart/form-data" - File exceeds
upload_max_filesizein php.ini - File input is missing the
nameattribute
// Form MUST have enctype:
// <form method="post" enctype="multipart/form-data">
// <input type="file" name="avatar">
// </form>
// Debug: check upload errors
if ($_FILES['avatar']['error'] !== UPLOAD_ERR_OK) {
echo "Upload error code: " . $_FILES['avatar']['error'];
// 1 = exceeds upload_max_filesize
// 2 = exceeds MAX_FILE_SIZE in form
// 3 = partial upload
// 4 = no file uploaded
}
🔹 "Failed to Move Uploaded File"
Cause: The destination directory doesn't exist or isn't writable by Apache.
# Create the uploads directory
sudo mkdir -p /var/www/html/uploads
# Give Apache write permission
sudo chown www-data:www-data /var/www/html/uploads
sudo chmod 755 /var/www/html/uploads
// Always check before moving:
$uploadDir = __DIR__ . '/uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$destination = $uploadDir . basename($_FILES['avatar']['name']);
if (move_uploaded_file($_FILES['avatar']['tmp_name'], $destination)) {
echo "Upload successful!";
} else {
echo "Upload failed — check directory permissions.";
}
🔹 Upload Size Limit Too Small
# Find the php.ini Apache is using
php -i | grep "Loaded Configuration File"
# Edit it
sudo nano /etc/php/8.3/apache2/php.ini
# Change these values:
# upload_max_filesize = 20M (default is 2M)
# post_max_size = 25M (must be larger than upload_max_filesize)
# max_execution_time = 120 (for slow uploads)
# Restart Apache
sudo service apache2 restart
WSL-Specific Issues
🔹 LAMP Services Don't Start Automatically
Cause: WSL doesn't use systemd by default, so services don't auto-start.
# Start services manually each session
sudo service apache2 start
sudo service mysql start
# Create a convenience alias — add to ~/.bashrc:
echo 'alias lamp-start="sudo service apache2 start && sudo service mysql start"' >> ~/.bashrc
source ~/.bashrc
# Now just type: lamp-start
🔹 Can't Access localhost from Windows Browser
Fix:
# Check if Apache is running
sudo service apache2 status
# Try the WSL IP address
hostname -I
# Use that IP in your browser: http://172.x.x.x/your_file.php
# In newer WSL2, localhost forwarding usually works.
# If not, restart WSL from PowerShell:
# wsl --shutdown
# Then reopen your Ubuntu terminal and start services again
🔹 File Permission Issues Between Windows & WSL
Symptom: Files created in Windows have wrong permissions in WSL.
# Work inside the WSL filesystem (not /mnt/c/) for best results
# Your web root: /var/www/html/
# If you must edit from Windows, fix permissions after:
sudo chown -R www-data:www-data /var/www/html/your_project/
sudo find /var/www/html/your_project/ -type d -exec chmod 755 {} \;
sudo find /var/www/html/your_project/ -type f -exec chmod 644 {} \;
General Debugging Strategies
🔹 The PHP Debugging Checklist
- Enable error display — Add
error_reporting(E_ALL)andini_set('display_errors', 1)at the top of your script. - Check the Apache error log —
sudo tail -20 /var/log/apache2/error.log - Use
var_dump()— The most useful debugging function. Shows type AND value. - Check the line ABOVE the reported error — Parse errors often point to the line after the actual mistake.
- Test in isolation — Create a small standalone script to test just the part that's broken.
- Read the error message carefully — PHP error messages usually tell you exactly what's wrong and on which line.
🔹 Useful Debugging Functions
// var_dump() — shows type and value (best for debugging)
var_dump($variable);
// print_r() — human-readable format for arrays/objects
echo "<pre>";
print_r($array);
echo "</pre>";
// gettype() — check the type of a variable
echo gettype($variable); // "string", "integer", "array", etc.
// debug_backtrace() — see the call stack
print_r(debug_backtrace());
// error_log() — write to the error log (doesn't show in browser)
error_log("Debug: value of \$x is $x");
// View with: sudo tail -f /var/log/apache2/error.log