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.html is 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 local REDIRECT_* 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

Administrative client hostbash
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.body
HTTP/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

Apache serverbash
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/null
VirtualHost 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.html

The request host decides which configuration matters

  • apachectl -S shows parsed virtual hosts, names, addresses, defaults, and source files.

  • HTTP and HTTPS may select different virtual hosts/configuration.

  • An inherited server/directory/.htaccess directive can override your assumption.

  • .htaccess use requires the applicable AllowOverride 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

Administrative client hostbash
curl -sS -D - -o /tmp/error-page.body \
  https://www.example.com/errors/404.html
head -c 500 /tmp/error-page.body
HTTP/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

/etc/apache2/sites-available/example-ssl.confapache
<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.html normally maps to /var/www/example/public/errors/404.html.

  • The <Directory> block applies authorization to the filesystem tree.

  • Require all granted is appropriate only for the public document root/error asset, not private directories.

  • AllowOverride None makes vhost configuration authoritative; adjust only when .htaccess is intentionally supported.

  • Use the actual virtual host root and least-privilege policy.

If the page lives outside DocumentRoot

/etc/apache2/sites-available/example-ssl.confapache
Alias /errors/ /srv/www/error-pages/
 
<Directory /srv/www/error-pages>
    Options -Indexes
    AllowOverride None
    Require all granted
</Directory>
 
ErrorDocument 404 /errors/404.html

Alias needs matching authorization

  • Alias maps 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 -Indexes prevents 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

Apache serverbash
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 readable
Each 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

/etc/apache2/sites-available/example-ssl.confapache
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.html

A fallback must escape the failing application path

  • The error directory exclusion appears before the front-controller rule.

  • -f and -d preserve real static files/directories.

  • END stops per-directory rewrite processing for the request in Apache 2.4.

  • Rewrite patterns differ between server/vhost and .htaccess contexts; 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 ErrorDocument may 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

/var/www/example/public/errors/404.phpphp
<?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-store is 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

Apache serverbash
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.log
Look 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 ErrorLog path may differ from distribution defaults.

  • Apache 2.4 can log ordinary missing-file messages at info, below a default warn threshold.

  • Use temporary per-module logging such as rewrite:trace only 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

Apache serverbash
sudo apachectl configtest
sudo apachectl -S
sudo systemctl reload apache2
sudo systemctl --no-pager --full status apache2
Syntax OK
... selected virtual hosts ...
Active: active (running)

Risk level: caution. Review the command before running it.

Reload only after syntax and mapping review

  • configtest catches directive syntax errors, not missing files or runtime permission/rewrite failures.

  • apachectl -S confirms 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

Administrative client hostbash
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 body

Success 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