This message describes two failures, not one. The first URL genuinely produced an error—often a normal 404. Apache then internally requested the configured custom error document, and that second request was missing, forbidden, redirected, rewritten, or otherwise broken. Fixing the fallback path removes the confusing extra sentence; it does not make the original missing resource exist.
Understand ErrorDocument path syntax
ErrorDocument 404 /errors/404.htmlis a local URL-path and triggers an internal redirect.The leading slash is URL-root-relative for the selected virtual host; it is not a filesystem pathname.
ErrorDocument 404 "Page not found"returns literal local text.A full
https://...action causes an external client redirect and loses localREDIRECT_*context; it can also turn the visible response into a 3xx flow.A relative action without the intended syntax may be treated as text rather than a file path.
1. Capture the exact behavior
curl -sS -D /tmp/missing.headers \
-o /tmp/missing.body \
https://www.example.com/__definitely_missing__
sed -n '1,20p' /tmp/missing.headers
head -c 500 /tmp/missing.bodyHTTP/2 404
content-type: text/html; charset=UTF-8
...
The response body may include the custom page plus “Additionally, a 404 Not Found error was encountered while trying to use an ErrorDocument...”Keep headers and body separate
A distinctive nonexistent path avoids testing a cached real route.
Headers prove the final status, content type, redirect/cache behavior, and serving layer.
The body reveals whether Apache used its built-in fallback or an application/proxy page.
Use a controlled path without secrets or user data.
Repeat through the origin and normal CDN/load-balancer path when intermediaries may replace errors.
2. Find the selected virtual host and directive
sudo apachectl -S
sudo apachectl -t -D DUMP_RUN_CFG
sudo grep -RIn --include="*.conf" \
--include=".htaccess" "^[[:space:]]*ErrorDocument" \
/etc/apache2 /var/www 2>/dev/nullVirtualHost configuration:
*:443 www.example.com (/etc/apache2/sites-enabled/example-ssl.conf:1)
...
/etc/apache2/sites-enabled/example-ssl.conf:18: ErrorDocument 404 /errors/404.htmlThe request host decides which configuration matters
apachectl -Sshows parsed virtual hosts, names, addresses, defaults, and source files.HTTP and HTTPS may select different virtual hosts/configuration.
An inherited server/directory/
.htaccessdirective can override your assumption..htaccessuse requires the applicableAllowOverride FileInfo; prefer main/vhost config when you administer the server.Inspect the parsed config rather than editing the first file named
default.
3. Request the error URI directly
curl -sS -D - -o /tmp/error-page.body \
https://www.example.com/errors/404.html
head -c 500 /tmp/error-page.bodyHTTP/2 200
content-type: text/html
... custom error page ...Direct access isolates the second failure
A static local error page normally returns 200 when requested directly.
404 means URL-to-file/application mapping is wrong or the file is absent.
403 means Apache authorization or filesystem traversal permissions deny access.
3xx may reveal forced redirects, canonical-host rules, authentication, or HTTP-to-HTTPS logic.
5xx points to a handler, proxy, include, or application failure; a static fallback is safer.
4. Map the URL to the correct filesystem target
<VirtualHost *:443>
ServerName www.example.com
DocumentRoot /var/www/example/public
ErrorDocument 404 /errors/404.html
<Directory /var/www/example/public>
Require all granted
AllowOverride None
</Directory>
</VirtualHost>This maps the URI under DocumentRoot
/errors/404.htmlnormally maps to/var/www/example/public/errors/404.html.The
<Directory>block applies authorization to the filesystem tree.Require all grantedis appropriate only for the public document root/error asset, not private directories.AllowOverride Nonemakes vhost configuration authoritative; adjust only when.htaccessis intentionally supported.Use the actual virtual host root and least-privilege policy.
If the page lives outside DocumentRoot
Alias /errors/ /srv/www/error-pages/
<Directory /srv/www/error-pages>
Options -Indexes
AllowOverride None
Require all granted
</Directory>
ErrorDocument 404 /errors/404.htmlAlias needs matching authorization
Aliasmaps a URL prefix to an explicit filesystem directory.The trailing slashes make prefix mapping intent clear.
The
<Directory>block authorizes the aliased filesystem location.Options -Indexesprevents directory listing if no index file exists.Avoid broad aliases that expose backups, source, configuration, logs, or secrets.
5. Check file and parent-directory access
namei -l /var/www/example/public/errors/404.html
stat -c "%A %U:%G %n" \
/var/www/example/public/errors/404.html
sudo -u www-data test -r \
/var/www/example/public/errors/404.html && echo readableEach parent directory needs traversal permission for the Apache worker identity, and the file must be readable.Do not solve permissions with chmod 777
Directories need execute/traverse permission; files need read permission for the serving identity.
Ownership/groups/ACLs should follow the deployment model.
World-writable web content enables tampering and may lead to code execution depending on handlers.
SELinux/AppArmor can deny access even when Unix mode bits look correct; check audit/system logs.
Run identity/path checks appropriate to the distribution/container—not blindly
www-data.
6. Exclude the error page from rewrites
RewriteEngine On
# Serve the error assets directly.
RewriteRule ^/errors/ - [END]
# Front controller for application routes only.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ /index.php [END]
ErrorDocument 404 /errors/404.htmlA fallback must escape the failing application path
The error directory exclusion appears before the front-controller rule.
-fand-dpreserve real static files/directories.ENDstops per-directory rewrite processing for the request in Apache 2.4.Rewrite patterns differ between server/vhost and
.htaccesscontexts; validate syntax for the actual location.Keep the error page independent of databases, sessions, templates, upstream proxies, and optional assets when resilience matters.
Authentication and authorization can break the fallback
A 404 inside a protected subtree may internally request an error URI that inherits protection.
An unauthenticated error page should not trigger a login redirect or leak protected resource existence.
Place resilient public error assets in an explicitly accessible location.
For 401, preserve the authentication semantics/headers; do not replace it casually with a public redirect.
Test anonymous, authenticated, forbidden, missing, and malformed requests separately.
Proxy and application-generated errors
A reverse proxy may return its upstream’s 404 body without Apache replacing it unless interception/configuration says otherwise.
A front controller can handle missing routes itself, so Apache
ErrorDocumentmay never run.A CDN/load balancer/WAF can replace or cache error responses.
Decide one owner per layer: edge, proxy, Apache, or application.
Keep infrastructure fallbacks available when the application/upstream is down, and test each failure mode.
Dynamic ErrorDocument handlers must preserve status
<?php
http_response_code(404);
header('Content-Type: text/html; charset=UTF-8');
header('Cache-Control: no-store');
?>
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Page not found</title></head>
<body>
<main>
<h1>Page not found</h1>
<p>The address may be incorrect or the page may have moved.</p>
<p><a href="/">Return to the home page</a></p>
</main>
</body>
</html>Static HTML is usually the more resilient choice
http_response_code(404)prevents a standalone/dynamic handler from accidentally reporting success.The page contains no reflected requested URL, query, stack trace, server path, or exception details.
A simple local link avoids dependency on routing helpers.
Cache policy should reflect edge/origin needs;
no-storeis conservative, not universally required.Dynamic code adds runtime failure modes; use it only when personalization/localization justifies them.
External redirects are usually wrong for 404 pages
A full URL action sends a client redirect rather than an internal local error response.
The browser may receive a 302/3xx and then a 200 error page, obscuring the original 404 semantics.
Local
REDIRECT_*context is not sent to external error URLs.Search engines and monitoring may interpret the flow as a soft 404 or redirect rather than the missing resource.
Prefer a local URL path unless a deliberate external architecture has explicit status/monitoring behavior.
Read both logs around one request
sudo journalctl -u apache2 --since "10 minutes ago" --no-pager
sudo tail -n 100 /var/log/apache2/example-error.log
sudo tail -n 100 /var/log/apache2/example-access.logLook for the original missing request, the internal `/errors/404.html` request/mapping failure, authorization/rewrite messages, and the final status.Logs explain which layer failed
The vhost
ErrorLogpath may differ from distribution defaults.Apache 2.4 can log ordinary missing-file messages at
info, below a defaultwarnthreshold.Use temporary per-module logging such as
rewrite:traceonly during bounded diagnosis; high trace levels are noisy and can expose request detail.Correlate timestamps/request IDs and sanitize logs before sharing.
Protect log directories; client-controlled content and sensitive URLs/headers may appear in logs.
Validate before applying
sudo apachectl configtest
sudo apachectl -S
sudo systemctl reload apache2
sudo systemctl --no-pager --full status apache2Syntax OK
... selected virtual hosts ...
Active: active (running)Risk level: caution. Review the command before running it.
Reload only after syntax and mapping review
configtestcatches directive syntax errors, not missing files or runtime permission/rewrite failures.apachectl -Sconfirms the host still maps to the intended vhost.A graceful reload applies valid config without a hard stop in normal deployments.
Service “active” does not prove the error behavior; run HTTP acceptance tests afterward.
Keep a tested rollback and avoid editing enabled generated/symlink targets inconsistently.
End-to-end acceptance test
curl -sS -o /tmp/direct.html -w \
"direct=%{http_code} type=%{content_type}\n" \
https://www.example.com/errors/404.html
curl -sS -o /tmp/missing.html -w \
"missing=%{http_code} type=%{content_type} redirects=%{num_redirects}\n" \
https://www.example.com/__definitely_missing__
cmp -s /tmp/direct.html /tmp/missing.html && echo "same body"direct=200 type=text/html
missing=404 type=text/html redirects=0
same bodySuccess means body and protocol are both correct
The error page works directly.
A missing path keeps status 404 and uses the intended content type.
No external redirect hides the original error.
The body is the custom page and no secondary ErrorDocument warning remains.
Repeat for HTTP/HTTPS, alternate hostnames, CDN/origin, anonymous/authenticated, and application/proxy outage cases in scope.
Useful 404 page design
State plainly that the page was not found.
Offer home, search, documentation, status, or contact paths that remain reliable.
Keep assets small/local and avoid application/database dependencies.
Do not expose filesystem paths, server versions, stack traces, request headers, or private routing details.
Keep one accessible H1, language, keyboard-visible links, responsive layout, and sufficient contrast.
Do not return 200 for missing URLs; attractive soft-404 pages still damage monitoring and search behavior.
Symptom-to-cause map
Direct error URI is 404: wrong URL mapping, file absent, wrong vhost/document root, or rewrite/front controller.
Direct error URI is 403: authorization, traversal permissions, SELinux/AppArmor, or
<Directory>mismatch.Direct URI redirects: canonical/TLS/auth/application rules capture it.
Direct URI is 500/502/503: dynamic handler or upstream dependency fails.
Missing URL returns 200: application/error handler lost original status.
Works on HTTP only: HTTPS vhost has different document root/directive/access rules.
Works at origin, fails through CDN: edge caches/replaces/errors or host/SNI routing differs.
Internal redirect limit exceeded: rewrite/ErrorDocument recursion.
Final checklist
Active host/scheme/vhost and all inherited ErrorDocument directives are identified.
Local error action is a URL path, correctly mapped and anonymously readable.
Error assets bypass fragile rewrites, authentication, proxies, and application dependencies.
Filesystem/MAC permissions follow least privilege; no world-writable shortcut was introduced.
Configuration passes syntax/vhost checks and reloads cleanly.
Direct error URI and genuine missing URL return expected body, status, content type, redirect count, and logs through origin/edge.
Monitoring distinguishes normal 404 volume from failures serving the fallback itself.
Official Apache references
Apache custom error responses documents local URL, external URL, literal text, and
REDIRECT_*behavior.Apache ErrorDocument directive defines syntax, contexts, and override requirements.
URL-to-filesystem mapping explains DocumentRoot, Alias, fallback resources, and 404 mapping.
Apache log files explains ErrorLog, per-module levels, correlation, and log security.
Apache httpd command reference documents syntax/config/vhost/module dump options.
Comments and corrections