What's Happening
Before PHP runs a single line, it reads your entire script top-to-bottom and checks the syntax. Hit a missing semicolon, an unclosed bracket, a stray character โ it stops right there and throws a Parse error. The script never executes.
The error looks like this:
Parse error: syntax error, unexpected token "echo", expecting "," or ";" in /var/www/html/index.php on line 15
PHP is saying: "I was expecting a comma or semicolon to close out the previous statement, but I found echo instead." The key insight: the real problem is almost never on the reported line. It's usually the line above it โ where you left something unclosed.
Debug Process
1. Check the line before the reported line
Go to line 14 โ one above the reported line 15. Nine times out of ten, there's a missing semicolon sitting right there.
<?php
// Line 14 โ no semicolon at the end
$message = "Hello, world"
echo $message; // Line 15 โ PHP trips here
Add the semicolon and you're done:
$message = "Hello, world";
echo $message;
2. Run PHP syntax check from the terminal
Skip the guessing. Use php -l (lint) to get the exact error without actually running the script:
php -l /var/www/html/index.php
Clean file:
No syntax errors detected in /var/www/html/index.php
Broken file:
Parse error: syntax error, unexpected token "echo", expecting "," or ";" in /var/www/html/index.php on line 15
Make this part of your workflow โ run it before every browser test.
3. Enable error display during development
Seeing a blank white page? PHP is hiding the error. Add these three lines at the top of your script temporarily:
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
Prefer to fix it in php.ini instead:
display_errors = On
error_reporting = E_ALL
After editing php.ini, restart your server:
sudo systemctl restart php8.2-fpm
# or
sudo systemctl restart apache2
Common Causes and Fixes
Missing semicolon
Responsible for the majority of these errors. Every PHP statement ends with ; โ no exceptions.
// Wrong
$name = "Alice"
echo $name;
// Fixed
$name = "Alice";
echo $name;
Mismatched or unclosed quotes
Open a quote, forget to close it, and PHP will keep reading until it finds a matching quote โ consuming everything in between, including newlines.
// Wrong โ single quote opened, never closed
$sql = 'SELECT * FROM users WHERE id = 1;
echo $sql;
// Fixed
$sql = 'SELECT * FROM users WHERE id = 1';
echo $sql;
Unclosed parenthesis or bracket
// Wrong โ missing closing parenthesis
if ($x > 0 {
echo "positive";
}
// Fixed
if ($x > 0) {
echo "positive";
}
Heredoc syntax error
Heredoc has strict formatting rules. On PHP older than 7.3, the closing identifier must sit at column 0 โ no leading spaces, no trailing spaces, just the label and a semicolon.
// Wrong on PHP < 7.3 โ closing tag is indented
$text = <<<EOT
Some text
EOT;
// Fixed for all PHP versions โ closing tag at column 0
$text = <<<EOT
Some text
EOT;
// PHP 7.3+ also accepts consistent indentation on the closing tag
$text = <<<EOT
Some text
EOT;
PHP 8 syntax on a PHP 7 server
Named arguments, match expressions, and nullsafe operators (?->) all require PHP 8.0+. Running that code on PHP 7 produces a parse error immediately.
# Check which version is actually running
php --version
# Named arguments โ PHP 8.0+ only
array_slice(array: $arr, offset: 0, length: 3); // Fails on PHP 7.x
Either upgrade to PHP 8 or rewrite the affected code to use positional arguments.
BOM or invisible characters
Files pasted from a Word document or saved with UTF-8 BOM encoding often carry invisible bytes before <?php. PHP sees them, gets confused, and dies before it even reaches your code.
# Check for BOM
file /var/www/html/index.php
# Strip it with sed
sed -i '1s/^\xEF\xBB\xBF//' /var/www/html/index.php
Verify the Fix
Once you've made your change, lint first โ then test in the browser. Don't skip the lint step.
php -l /var/www/html/index.php
Then run the script directly or hit it via HTTP:
php /var/www/html/index.php
# or
curl -I http://localhost/index.php
Running Nginx + PHP-FPM? Also check the FPM error log โ it often shows errors that don't surface in the browser:
sudo tail -f /var/log/php8.2-fpm.log
Lessons Learned
- The reported line is rarely the broken line. Look one or two lines above it. The unclosed expression starts there, not where PHP complains.
- Run
php -lconstantly. Wire it into your editor's save action or a pre-commit hook. Syntax errors have no business reaching the server. - Turn off display_errors in production. Parse errors expose file paths and internal code structure. Log them server-side; never print them to users.
- Install a real-time linter. PHP Intelephense for VS Code or PhpStorm's built-in inspector catch syntax errors as you type โ no waiting until runtime to find out you forgot a semicolon.

