Skip to content

Fix: Apache 500 Internal Server Error

FixDevs ·

Quick Answer

Resolve Apache's 500 Internal Server Error by checking error logs, fixing .htaccess rules, correcting file permissions, and debugging PHP/CGI configuration.

The Error

You load a page served by Apache and get:

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Or in your browser, a plain white page with:

500 Internal Server Error

In your Apache error log (/var/log/apache2/error.log on Debian/Ubuntu, /var/log/httpd/error_log on RHEL/CentOS), you see one of these:

[core:alert] [pid 12345] /var/www/html/.htaccess: Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration
[cgid:error] [pid 12345] [client 192.168.1.10:54321] End of script output before headers: index.cgi
[proxy_fcgi:error] [pid 12345] (70007)The timeout specified has expired: AH01075: Error dispatching request to :
AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error

All of these result in the same 500 status code sent to the client, but the root causes are entirely different. The error log is the only way to tell them apart.

Why This Happens

A 500 Internal Server Error is Apache’s generic “something went wrong on the server side” response. Unlike a 404 (page not found) or a 403 (forbidden), a 500 doesn’t tell the client what broke — it just says the server failed to process the request.

Common causes:

  • Syntax errors in .htaccess files. A single typo or unsupported directive crashes request processing for the entire directory tree.
  • Missing Apache modules. Your config references mod_rewrite, mod_headers, or another module that isn’t loaded.
  • Incorrect file or directory permissions. Apache can’t read the files it needs to serve, or it can’t write to directories required by your application.
  • PHP fatal errors. A syntax error, missing extension, or exceeded memory limit kills the PHP process mid-request.
  • CGI script failures. A CGI script doesn’t output proper HTTP headers, lacks execute permissions, or has the wrong shebang line.
  • SELinux blocking file access. On RHEL/CentOS/Fedora, SELinux prevents Apache from reading files outside its allowed context.
  • PHP-FPM communication failure. Apache can’t reach the PHP-FPM socket or the worker pool is exhausted.
  • Infinite redirect loops. Misconfigured RewriteRule directives create a loop that Apache detects and aborts.
  • Exceeded resource limits. PHP runs out of memory, or Apache hits its LimitRequestBody threshold.

Fix 1: Check the Apache Error Log

Every single 500 fix starts here. The error log tells you exactly what went wrong.

Debian/Ubuntu:

sudo tail -50 /var/log/apache2/error.log

RHEL/CentOS/Fedora:

sudo tail -50 /var/log/httpd/error_log

If you have virtual hosts with custom log paths, find them:

sudo grep -r "ErrorLog" /etc/apache2/sites-enabled/ 2>/dev/null
sudo grep -r "ErrorLog" /etc/httpd/conf.d/ 2>/dev/null

Then tail that specific log. If you’re not sure which virtual host is handling the request:

# Watch the log in real time while you trigger the error in your browser
sudo tail -f /var/log/apache2/error.log

The error message you see here determines which fix below applies. Don’t guess — read the log.

Fix 2: Fix .htaccess Syntax Errors

The most common cause of 500 errors. A single bad line in .htaccess takes down the entire directory.

Find the offending .htaccess file:

The error log will point to the exact file and line. If it says:

/var/www/html/mysite/.htaccess: Invalid command 'RewirteEngine'

That’s a typo (RewirteEngine instead of RewriteEngine). Open the file and fix it.

Common .htaccess mistakes:

# WRONG: Typo in directive name
RewirteEngine On

# RIGHT:
RewriteEngine On
# WRONG: Missing RewriteEngine On before rules
RewriteRule ^old-page$ /new-page [R=301,L]

# RIGHT: Must enable the engine first
RewriteEngine On
RewriteRule ^old-page$ /new-page [R=301,L]
# WRONG: Using directives from a module that isn't loaded
<IfModule !mod_rewrite.c>
    # This block runs when mod_rewrite is NOT available
    RewriteEngine On  # This will fail
</IfModule>

# RIGHT: Wrap in a positive check
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^old-page$ /new-page [R=301,L]
</IfModule>

Test your Apache config without restarting:

sudo apachectl configtest

If you see Syntax OK, the main config is fine. Note that configtest doesn’t validate .htaccess files — those are parsed at request time. The only way to test .htaccess is to trigger an actual request and check the error log.

Temporarily disable .htaccess to confirm it’s the problem:

Rename it and reload:

mv /var/www/html/mysite/.htaccess /var/www/html/mysite/.htaccess.bak

If the 500 error disappears, the problem is in that file. Add lines back one by one until you find the bad one.

Fix 3: Enable Missing Apache Modules

Your config or .htaccess uses a directive from a module that isn’t loaded.

Check loaded modules:

sudo apachectl -M

Common modules that cause 500 errors when missing:

# mod_rewrite -- needed for URL rewriting (most WordPress/Laravel/Drupal sites)
sudo a2enmod rewrite

# mod_headers -- needed for custom HTTP headers, CORS
sudo a2enmod headers

# mod_expires -- needed for cache control headers
sudo a2enmod expires

# mod_ssl -- needed for HTTPS
sudo a2enmod ssl

# mod_proxy and mod_proxy_fcgi -- needed for PHP-FPM
sudo a2enmod proxy proxy_fcgi

On RHEL/CentOS, modules are loaded via config files in /etc/httpd/conf.modules.d/. Check if the module’s .so file exists:

ls /usr/lib64/httpd/modules/ | grep rewrite

If the module file exists but isn’t loaded, add it:

# /etc/httpd/conf.modules.d/00-base.conf
LoadModule rewrite_module modules/mod_rewrite.so

Restart Apache after enabling modules:

sudo systemctl restart apache2   # Debian/Ubuntu
sudo systemctl restart httpd     # RHEL/CentOS

Pro Tip: Wrap module-specific directives in <IfModule mod_name.c> blocks in your .htaccess. This way, a missing module causes a silent skip instead of a 500 error. This is especially important for portable .htaccess files that may run on different servers with different module sets.

Fix 4: Fix File and Directory Permissions

Apache needs to read your files and traverse your directories. Incorrect permissions cause a 500 error (or sometimes a 403, depending on the configuration).

Standard permissions for web files:

# Directories: 755 (owner rwx, group rx, others rx)
sudo find /var/www/html/mysite -type d -exec chmod 755 {} \;

# Files: 644 (owner rw, group r, others r)
sudo find /var/www/html/mysite -type f -exec chmod 644 {} \;

Set the correct owner:

# Apache runs as www-data on Debian/Ubuntu
sudo chown -R www-data:www-data /var/www/html/mysite

# Apache runs as apache on RHEL/CentOS
sudo chown -R apache:apache /var/www/html/mysite

Check which user Apache runs as:

grep -E "^User|^Group" /etc/apache2/apache2.conf 2>/dev/null
grep -E "^User|^Group" /etc/httpd/conf/httpd.conf 2>/dev/null

Writable directories for uploads or cache:

Some applications need Apache to write to specific directories (upload folders, cache directories, log directories):

sudo chmod 775 /var/www/html/mysite/storage
sudo chmod 775 /var/www/html/mysite/storage/logs
sudo chmod 775 /var/www/html/mysite/bootstrap/cache

Related: For more on Unix permission errors, see Fix: bash: Permission Denied.

Fix 5: Fix mod_rewrite and Redirect Loops

Misconfigured rewrite rules cause infinite redirect loops. Apache detects these after 10 iterations and returns a 500.

The error log will show:

AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error

Common redirect loop scenarios:

# WRONG: This rewrites /page to /page, which triggers another rewrite to /page...
RewriteEngine On
RewriteRule ^(.*)$ /index.php [L]

The fix is to add a condition that stops the loop:

RewriteEngine On

# Don't rewrite if the request is already for an existing file or directory
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php [L]

HTTPS redirect loops are another classic:

# WRONG: If Apache doesn't see HTTPS (e.g., behind a load balancer that terminates SSL),
# this creates an infinite redirect loop
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

If Apache sits behind a reverse proxy or load balancer that handles SSL, the connection between the proxy and Apache is plain HTTP. Apache sees HTTPS=off on every request and keeps redirecting.

Fix by checking the X-Forwarded-Proto header instead:

RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

Also make sure AllowOverride is set correctly. If .htaccess rewrite rules are being ignored (and the fallback behavior causes errors), check your virtual host:

<Directory /var/www/html/mysite>
    AllowOverride All
</Directory>

AllowOverride None disables .htaccess entirely. AllowOverride All allows all directives.

Fix 6: Fix PHP Errors

PHP fatal errors return a 500 to the client. The Apache error log will include the PHP error, or PHP may log to its own file.

Check PHP’s error log:

# Find where PHP logs errors
php -i | grep error_log

# Common locations
sudo tail -50 /var/log/php_errors.log
sudo tail -50 /var/log/php8.3-fpm.log

Common PHP causes of 500 errors:

  1. Syntax error in PHP code:
Parse error: syntax error, unexpected '}' in /var/www/html/mysite/index.php on line 42

Check the file at that line and fix the syntax.

  1. Missing PHP extension:
Fatal error: Uncaught Error: Call to undefined function mysqli_connect()

Install the missing extension:

sudo apt install php8.3-mysql    # Debian/Ubuntu
sudo dnf install php-mysqlnd     # RHEL/CentOS
sudo systemctl restart apache2
  1. Memory limit exceeded:
Fatal error: Allowed memory size of 134217728 bytes exhausted

Increase in php.ini:

; /etc/php/8.3/apache2/php.ini
memory_limit = 256M

Or per-directory in .htaccess:

php_value memory_limit 256M

Enable error display temporarily for debugging (never in production):

# In .htaccess
php_flag display_errors on
php_flag log_errors on

This shows the actual PHP error in the browser instead of a generic 500 page.

Related: For PHP memory issues with Composer, see Fix: PHP Composer Memory Limit.

Fix 7: Fix CGI Script Issues

If you’re running CGI scripts (Perl, Python, Bash), several things can cause a 500.

The error log will typically show:

End of script output before headers: myscript.cgi

This means the script either didn’t produce any output, or it didn’t output valid HTTP headers before the body.

1. Missing or wrong shebang line:

# WRONG: Missing shebang -- Apache doesn't know how to execute the script
echo "Content-Type: text/html"
echo ""
echo "Hello World"

# RIGHT: Include the interpreter path
#!/usr/bin/perl
print "Content-Type: text/html\n\n";
print "Hello World";

2. Missing execute permission:

chmod +x /var/www/cgi-bin/myscript.cgi

3. Wrong line endings. If the script was created or edited on Windows, it may have \r\n line endings instead of \n. The shebang line becomes #!/usr/bin/perl\r, and Linux can’t find the interpreter.

# Fix line endings
sed -i 's/\r$//' /var/www/cgi-bin/myscript.cgi

4. Script must output headers first:

#!/usr/bin/python3
# WRONG: Body before headers
print("Hello World")

# RIGHT: Headers, blank line, then body
print("Content-Type: text/html")
print("")
print("Hello World")

5. Make sure CGI is enabled:

sudo a2enmod cgid
sudo systemctl restart apache2

Fix 8: Fix SELinux Contexts (RHEL/CentOS/Fedora)

On RHEL-based distributions, SELinux prevents Apache from reading files that don’t have the correct security context, even if Unix permissions are fine.

Check if SELinux is the problem:

# Is SELinux enforcing?
getenforce

# Check for recent denials involving Apache
sudo grep httpd /var/log/audit/audit.log | grep denied | tail -20

Fix the file context for your web directory:

# Set the correct SELinux context for web content
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/html/mysite(/.*)?"
sudo restorecon -Rv /var/www/html/mysite

If Apache needs to write to a directory (uploads, cache, logs):

sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/html/mysite/storage(/.*)?"
sudo restorecon -Rv /var/www/html/mysite/storage

If Apache needs to make network connections (to a database, API, or mail server):

sudo setsebool -P httpd_can_network_connect 1

If Apache needs to connect to a database specifically:

sudo setsebool -P httpd_can_network_connect_db 1

If Apache needs to send mail:

sudo setsebool -P httpd_can_sendmail 1

Common Mistake: Disabling SELinux entirely (setenforce 0 or setting SELINUX=disabled in /etc/selinux/config) “fixes” the problem but removes an important security layer. Always use targeted booleans and file contexts instead. If you temporarily set setenforce 0 and the error goes away, you’ve confirmed SELinux is the cause — now fix it properly with the commands above.

Fix 9: Fix PHP-FPM Communication

If you’re using PHP-FPM with Apache (via mod_proxy_fcgi), the connection between Apache and PHP-FPM can fail.

Check if PHP-FPM is running:

sudo systemctl status php8.3-fpm

If it’s stopped or crashed:

sudo systemctl start php8.3-fpm
sudo journalctl -u php8.3-fpm --no-pager -n 50

Related: If you see service failures in systemd, see Fix: systemctl Service Failed.

Verify the socket or port matches:

Check what PHP-FPM is listening on:

sudo grep "^listen " /etc/php/8.3/fpm/pool.d/www.conf

Check what Apache expects:

sudo grep -r "proxy:fcgi\|proxy:unix" /etc/apache2/sites-enabled/ 2>/dev/null
sudo grep -r "proxy:fcgi\|proxy:unix" /etc/httpd/conf.d/ 2>/dev/null

If using a Unix socket:

Apache config should look like:

<FilesMatch \.php$>
    SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
</FilesMatch>

If using TCP:

PHP-FPM listens on 127.0.0.1:9000, and Apache connects to:

<FilesMatch \.php$>
    SetHandler "proxy:fcgi://127.0.0.1:9000"
</FilesMatch>

PHP-FPM worker pool exhaustion:

If all PHP-FPM workers are busy, new requests queue up and eventually time out. Check worker status:

# Enable PHP-FPM status page in pool config
# pm.status_path = /status

# Then check it
curl http://localhost/status

Increase workers in /etc/php/8.3/fpm/pool.d/www.conf:

pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20

Restart PHP-FPM after changes:

sudo systemctl restart php8.3-fpm

Fix 10: Fix Memory and Resource Limits

PHP or Apache hitting resource limits causes a 500.

PHP memory limit:

Fatal error: Allowed memory size of 134217728 bytes exhausted

Edit php.ini:

# Find the right php.ini
php --ini | grep "Loaded Configuration"

# Typical location for Apache module
# /etc/php/8.3/apache2/php.ini
# Typical location for PHP-FPM
# /etc/php/8.3/fpm/php.ini
memory_limit = 256M
max_execution_time = 120
post_max_size = 64M
upload_max_filesize = 64M

Apache request body limit:

If users are uploading large files and getting a 500, check LimitRequestBody:

# In your virtual host or .htaccess
# Allow up to 100MB uploads
LimitRequestBody 104857600

The default is unlimited (0), but some hosting providers set restrictive limits.

Apache process limits:

If Apache runs out of worker processes, new requests fail. Check your MPM configuration:

sudo apachectl -V | grep MPM

For the prefork MPM (common with mod_php):

<IfModule mpm_prefork_module>
    StartServers          5
    MinSpareServers       5
    MaxSpareServers       10
    MaxRequestWorkers     150
    MaxConnectionsPerChild 3000
</IfModule>

For the event MPM (common with PHP-FPM):

<IfModule mpm_event_module>
    StartServers          3
    MinSpareThreads       75
    MaxSpareThreads       250
    ThreadsPerChild       25
    MaxRequestWorkers     400
    MaxConnectionsPerChild 1000
</IfModule>

Restart Apache after any MPM changes:

sudo systemctl restart apache2

Fix 11: Fix Ownership of the Document Root Path

Apache needs to traverse every directory in the path from / to your document root. A common oversight is that one of the parent directories isn’t accessible.

Check the full path:

# Test each directory in the path
namei -l /var/www/html/mysite

This shows permissions for every component. If any directory is missing x (execute) permission for the Apache user, you get a 500 or 403.

# Common fix: Ensure the home directory is traversable if serving from /home/user/public_html
chmod 711 /home/username

Virtual host DocumentRoot must exist:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html/mysite
</VirtualHost>

If /var/www/html/mysite doesn’t exist, Apache returns a 500 on any request to this virtual host. Create the directory:

sudo mkdir -p /var/www/html/mysite
sudo chown www-data:www-data /var/www/html/mysite

Related: For SSH key permission issues that might affect deployment, see Fix: Git Permission Denied (publickey).

Fix 12: Debug with a Minimal Test

When all else fails, isolate the problem by creating a minimal test file.

Test basic Apache serving:

echo "<h1>Apache works</h1>" | sudo tee /var/www/html/test.html
curl http://localhost/test.html

If this works, Apache itself is fine. The problem is in your application or configuration.

Test PHP:

echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/info.php
curl -I http://localhost/info.php

If you get a 500, PHP is misconfigured. If you get a 200 but see raw PHP code, PHP isn’t being processed (module or FPM not configured).

Test .htaccess processing:

echo "This is invalid syntax on purpose" | sudo tee /var/www/html/.htaccess
curl -I http://localhost/

If you get a 500, .htaccess is being processed. If you get a 200, AllowOverride is set to None and .htaccess files are being ignored.

Remove the test files after debugging:

sudo rm /var/www/html/test.html /var/www/html/info.php

Increase Apache log verbosity temporarily:

# In your virtual host config
LogLevel debug

This floods the error log with details but helps diagnose obscure issues. Set it back to warn or error when done:

sudo systemctl reload apache2

Still Not Working?

Narrow it down systematically

  1. Check the error log. This is step one, always. The error log tells you what broke.
  2. Determine if it’s Apache, PHP, or your app. Serve a plain HTML file. If that works, serve a phpinfo() page. If that works, the problem is in your application code.
  3. Disable .htaccess. Rename it and test. If the error disappears, add rules back one at a time.
  4. Check if it’s a specific URL. If only /admin throws a 500 but / works, the problem is in the code that handles /admin.

Check for disk space issues

A full disk causes all sorts of 500 errors — Apache can’t write logs, PHP can’t write sessions, your app can’t write temp files:

df -h

If any partition is at 100%, free up space. Common culprits: old log files, PHP session files, temp files.

# Check log sizes
sudo du -sh /var/log/apache2/ /var/log/httpd/ 2>/dev/null

# Clean old PHP sessions
sudo find /var/lib/php/sessions/ -type f -mtime +7 -delete

Related: For Docker disk space issues, see Fix: Docker No Space Left on Device.

Permissions look right but it still fails

If file permissions are correct (644 for files, 755 for directories) and the owner matches the Apache user, but you still get a 500, check:

  • ACLs (Access Control Lists): getfacl /var/www/html/mysite might show restrictive ACL entries overriding standard Unix permissions.
  • File attributes: lsattr /var/www/html/mysite/index.php might show an immutable flag.
  • SELinux (see Fix 8 above).
  • AppArmor (on Ubuntu): Check sudo aa-status and look for Apache profiles that might restrict file access.

The error only happens under load

Intermittent 500 errors that increase with traffic usually mean a resource limit is being hit:

  • PHP-FPM workers exhausted (see Fix 9).
  • Apache MaxRequestWorkers reached (see Fix 10).
  • Database connection limit hit (check your database logs).
  • Disk I/O saturation (check iostat or iotop).

Monitor in real time:

# Watch Apache status (enable mod_status first)
sudo a2enmod status
# Then access http://localhost/server-status

# Watch system resources
top -u www-data

Related: For Nginx reverse proxy issues, see Fix: Nginx 502 Bad Gateway. For connection issues reaching your server, see Fix: curl Failed to Connect. For permission errors when deploying via SSH, see Fix: bash: Permission Denied.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles