Skip to main content

🔧 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 .php extension 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 method is GET but you're reading $_POST
  • Form fields are missing the name attribute
  • Form action points 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:// and https:// or localhost and 127.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_filesize in php.ini
  • File input is missing the name attribute

// 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

  1. Enable error display — Add error_reporting(E_ALL) and ini_set('display_errors', 1) at the top of your script.
  2. Check the Apache error logsudo tail -20 /var/log/apache2/error.log
  3. Use var_dump() — The most useful debugging function. Shows type AND value.
  4. Check the line ABOVE the reported error — Parse errors often point to the line after the actual mistake.
  5. Test in isolation — Create a small standalone script to test just the part that's broken.
  6. 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