500 Internal Server Error in PHP/Apache
Síntomas
- Apache serves a blank white page or "500 Internal Server Error" with no additional detail
- Apache error log (`/var/log/apache2/error.log`) shows `PHP Fatal error: ...` or `.htaccess: Invalid command` entries
- The error is triggered on every request to a specific path or after a recent `.htaccess` change
- WordPress or other PHP CMS returns the 500 after a plugin update
- `phpinfo()` page works fine but the application itself does not — pointing to app-level rather than server-level failure
- Apache error log (`/var/log/apache2/error.log`) shows `PHP Fatal error: ...` or `.htaccess: Invalid command` entries
- The error is triggered on every request to a specific path or after a recent `.htaccess` change
- WordPress or other PHP CMS returns the 500 after a plugin update
- `phpinfo()` page works fine but the application itself does not — pointing to app-level rather than server-level failure
Causas raíz
- Syntax error in `.htaccess` — an invalid directive or RewriteRule causes Apache to refuse all requests to that directory
- PHP Fatal Error in application code — uncaught exception or calling an undefined function crashes the PHP process before output is sent
- File or directory permissions are too restrictive — Apache cannot read a PHP file or write to a cache directory (needs 644 files, 755 dirs)
- PHP memory limit exceeded — `memory_limit` in `php.ini` is too low for the operation, causing a fatal error mid-execution
- Missing PHP extension — code calls a function from `pdo_mysql`, `gd`, or another extension that isn't installed or enabled
Diagnóstico
**Step 1: Read the Apache error log**
```bash
sudo tail -n 50 /var/log/apache2/error.log
# Or for Nginx + PHP-FPM:
sudo tail -n 50 /var/log/php8.2-fpm.log
```
**Step 2: Enable PHP error display temporarily**
```php
// Add to the top of index.php (dev/staging only!)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
```
**Step 3: Check .htaccess syntax**
```bash
# Test Apache config including .htaccess interpretation
sudo apache2ctl configtest
# Temporarily rename to isolate
mv /var/www/html/.htaccess /var/www/html/.htaccess.bak
```
**Step 4: Check file permissions**
```bash
ls -la /var/www/html/
# Files should be 644, directories 755
# Apache user (www-data) must be able to read them
sudo -u www-data stat /var/www/html/index.php
```
**Step 5: Check PHP memory limit**
```bash
php -i | grep memory_limit
# Or create info.php: <?php phpinfo(); ?>
```
```bash
sudo tail -n 50 /var/log/apache2/error.log
# Or for Nginx + PHP-FPM:
sudo tail -n 50 /var/log/php8.2-fpm.log
```
**Step 2: Enable PHP error display temporarily**
```php
// Add to the top of index.php (dev/staging only!)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
```
**Step 3: Check .htaccess syntax**
```bash
# Test Apache config including .htaccess interpretation
sudo apache2ctl configtest
# Temporarily rename to isolate
mv /var/www/html/.htaccess /var/www/html/.htaccess.bak
```
**Step 4: Check file permissions**
```bash
ls -la /var/www/html/
# Files should be 644, directories 755
# Apache user (www-data) must be able to read them
sudo -u www-data stat /var/www/html/index.php
```
**Step 5: Check PHP memory limit**
```bash
php -i | grep memory_limit
# Or create info.php: <?php phpinfo(); ?>
```
Resolución
**Fix 1: Fix a .htaccess syntax error**
```apache
# Common .htaccess for SPA or WordPress
Options -Indexes
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
```
**Fix 2: Fix file permissions**
```bash
sudo find /var/www/html -type f -exec chmod 644 {} \;
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo chown -R www-data:www-data /var/www/html/
```
**Fix 3: Increase PHP memory limit**
```ini
; /etc/php/8.2/apache2/php.ini
memory_limit = 256M
max_execution_time = 60
```
```bash
sudo systemctl restart apache2
```
**Fix 4: Enable a missing PHP extension**
```bash
sudo apt install php8.2-gd php8.2-pdo-mysql
sudo phpenmod gd pdo_mysql
sudo systemctl restart apache2
```
```apache
# Common .htaccess for SPA or WordPress
Options -Indexes
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
```
**Fix 2: Fix file permissions**
```bash
sudo find /var/www/html -type f -exec chmod 644 {} \;
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo chown -R www-data:www-data /var/www/html/
```
**Fix 3: Increase PHP memory limit**
```ini
; /etc/php/8.2/apache2/php.ini
memory_limit = 256M
max_execution_time = 60
```
```bash
sudo systemctl restart apache2
```
**Fix 4: Enable a missing PHP extension**
```bash
sudo apt install php8.2-gd php8.2-pdo-mysql
sudo phpenmod gd pdo_mysql
sudo systemctl restart apache2
```
Prevención
- **Log to file, not display**: In production always set `display_errors = Off` and `log_errors = On` pointing to a writable log path — never expose tracebacks to end users
- **Composer autoload**: Use `vendor/autoload.php` from Composer rather than manual `require` chains — missing files become clear errors
- **Staging environment**: Test plugin/theme updates on a staging clone before applying to production WordPress sites
- **Version control .htaccess**: Keep `.htaccess` in git so syntax regressions are caught in review and easily reverted
- **PHP linting in CI**: Run `php -l *.php` in CI to catch fatal parse errors before they reach the server
- **Composer autoload**: Use `vendor/autoload.php` from Composer rather than manual `require` chains — missing files become clear errors
- **Staging environment**: Test plugin/theme updates on a staging clone before applying to production WordPress sites
- **Version control .htaccess**: Keep `.htaccess` in git so syntax regressions are caught in review and easily reverted
- **PHP linting in CI**: Run `php -l *.php` in CI to catch fatal parse errors before they reach the server