Fixing Apache errors: 403, 404, 500, 502, 503 and startup failures

Updated November 2026

The page a visitor sees ("Forbidden", "Internal Server Error") rarely says why. The error log almost always does, and since httpd 2.4 most messages carry an AH number you can search for. This page lists the messages you are most likely to meet, what each one means, and the fix. Every message quoted here was checked against the httpd 2.4 source code or reproduced on a test server running 2.4.69, the current release as of November 2026; paths and numbers in your log will differ.

Find and read the error log

SystemMain error logService name
Debian, Ubuntu/var/log/apache2/error.logapache2
RHEL, AlmaLinux, Rocky, Fedora/var/log/httpd/error_loghttpd

Virtual hosts often have their own ErrorLog; apachectl -S shows which file each site uses. Watch the log while you reproduce the problem:

sudo tail -f /var/log/apache2/error.log        # RHEL: /var/log/httpd/error_log
sudo journalctl -u apache2 -n 50 --no-pager     # RHEL: -u httpd; shows errors when the service will not start

A typical line looks like this:

[Wed Nov 04 02:10:28.799290 2026] [authz_core:error] [pid 2212119:tid 2212122] [client 203.0.113.7:45168] AH01630: client denied by server configuration: /var/www/example/private/

Read it left to right: time, module:level, process and thread, client address, then the message with its AH code. Some messages, such as "File does not exist", are logged at info level in 2.4 and stay hidden under the default LogLevel warn. To see them temporarily, set LogLevel warn core:info in the virtual host and reload. More in logging.

Always test after editing and before reloading:

sudo apachectl configtest
Syntax OK
sudo systemctl reload apache2      # RHEL/Fedora: sudo systemctl reload httpd

Startup problems

AH00558: could not reliably determine the server's name

AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1. Set the 'ServerName' directive globally to suppress this message

A warning, not an error: Apache still starts. It appears when there is no ServerName at server level. Set one:

# Debian/Ubuntu
echo "ServerName www.example.com" | sudo tee /etc/apache2/conf-available/servername.conf
sudo a2enconf servername
# RHEL/Fedora: add "ServerName www.example.com:80" to /etc/httpd/conf/httpd.conf
sudo apachectl configtest

Address already in use

(98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80
no listening sockets available, shutting down
AH00015: Unable to open logs

Another program already listens on that port: a second Apache instance, nginx, a container publishing port 80, or an old httpd that did not stop. The last line is a side effect; ignore it until the port is free. Find the owner:

sudo ss -ltnp 'sport = :80'

Stop or reconfigure that program, or change Apache's Listen line (/etc/apache2/ports.conf on Debian, /etc/httpd/conf/httpd.conf on RHEL). Two identical Listen lines give a different, clearer error at configtest: "Cannot define multiple Listeners on the same IP:port".

SELinux refuses a new port (RHEL, Fedora)

If httpd fails to start after you add a port such as Listen 9876, and /var/log/audit/audit.log shows avc: denied { name_bind }, the port is not labelled for web servers. List and add it:

sudo semanage port -l | grep -w http_port_t
sudo semanage port -a -t http_port_t -p tcp 9876

403 Forbidden

Four different causes, each with its own message.

AH01630: client denied by server configuration: /path
A Require rule refused the request. Most often the site lives outside /var/www: both Debian and RHEL ship <Directory /> Require all denied and only grant /var/www (Debian also /usr/share). Add a block for your directory:
<Directory "/srv/example/public">
    Require all granted
</Directory>
Also look for old Order deny,allow / Deny from all lines (2.2 syntax) mixed in. See access control.
AH01276: Cannot serve directory /path/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive
A directory URL with no index file, and listings are off. Upload index.html (or index.php and add it to DirectoryIndex). Turning Options +Indexes on is rarely what you want.
AH00035: access to /page denied (filesystem path '/path') because search permissions are missing on a component of the path
The Apache user (www-data on Debian, apache on RHEL) cannot enter one of the parent directories. A related message is AH00529: … pcfg_openfile: unable to check htaccess file, ensure it is readable and that '/path/' is executable. Show every directory's permissions on the way down:
namei -l /srv/example/public/index.html
sudo chmod o+x /srv /srv/example          # directories need x to be entered
sudo chmod o+r /srv/example/public/index.html
Home directories are often mode 700 or 750, which blocks the web server.
Permissions look right, but still 403 (RHEL, Fedora)
SELinux. Files created or moved outside /var/www get the wrong label, and files moved with mv from a home directory keep user_home_t. Check and fix:
ls -Z /srv/example/public/index.html
sudo ausearch -m AVC -ts recent
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/example(/.*)?"
sudo restorecon -R -v /srv/example
For directories the application must write to (uploads, cache), use httpd_sys_rw_content_t on just those paths. Do not switch SELinux off to "fix" this.

404 Not Found

With core:info logging you will see AH00128: File does not exist: /var/www/html/missing.html. Compare that path with where the file really is. Typical causes:

500 Internal Server Error

/var/www/html/.htaccess: Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration
The .htaccess uses a module that is not loaded. On Debian/Ubuntu: sudo a2enmod rewrite and reload. The same pattern applies to Header (headers), Expires… (expires) and others. If the message ends in "not allowed here" instead, the module is loaded but AllowOverride forbids that directive in .htaccess.
AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error
A rewrite loop: a rule rewrites to a URL that matches the same rule again. Add a condition that skips existing files (RewriteCond %{REQUEST_FILENAME} !-f) or use the [END] flag. For a front controller, FallbackResource /index.php is simpler.
End of script output before headers: script.cgi
A CGI script died or printed nothing before its headers. Run it by hand as the web user, check the #! line, the execute bit, and Windows line endings.
AH01071: Got error 'Primary script unknown'
PHP-FPM received a path to a PHP file that does not exist on its side. Check that the path Apache sends matches where PHP-FPM sees the files (containers and chroots often differ) and that the file exists. See PHP with Apache.

502, 503 and 504 behind mod_proxy

These codes mean Apache itself is fine but the application behind it is not answering properly.

503: backend not reachable
(111)Connection refused: AH00957: http: attempt to connect to 127.0.0.1:8080 (127.0.0.1:8080) failed
AH01114: HTTP: failed to make connection to backend: 127.0.0.1
Nothing listens on that address. Check the application and test it directly from the server: curl -I http://127.0.0.1:8080/ and sudo ss -ltn. For PHP-FPM over a socket the message is AH02454: FCGI: attempt to connect to Unix domain socket /run/php/php8.4-fpm.sock (localhost:8000) failed: the socket path in SetHandler is wrong, PHP-FPM is stopped, or the socket's permissions do not let Apache's user connect.
503 with "(13)Permission denied" on RHEL
SELinux blocks httpd from opening network connections. Allow it, persistently:
sudo setsebool -P httpd_can_network_connect 1
(httpd_can_network_connect_db is the narrower switch for database ports only.)
502: backend too slow or broke the connection
(70007)The timeout specified has expired: AH01102: error reading status line from remote server 127.0.0.1:8080
AH00898: Error reading from remote server returned by /app/report
With mod_proxy_http, a backend that does not answer within the proxy timeout gives 502, not 504. The timeout defaults to Timeout (60 seconds). Make the slow request faster if you can; otherwise raise it only for that path, for example ProxyPass "/app/" "http://127.0.0.1:8080/" timeout=180, or globally with ProxyTimeout. The same message without the timeout part means the backend closed the connection or sent something that is not HTTP: check the application's own log.
504: gateway timeout
mod_proxy_fcgi (PHP-FPM) returns 504 when PHP takes longer than the timeout, logging AH01075: Error dispatching request to …. Check PHP's own limits (max_execution_time, request_terminate_timeout) as well as Apache's.

More about timeouts and headers is in the reverse proxy guide. If errors only appear under load, look at busy workers in server-status: the log line AH00484: server reached MaxRequestWorkers setting means requests are queueing.

A quick checklist

Sources