[PATCH] docs: generate a static 404 page

From: Rito Rhymes

Date: Sun Mar 29 2026 - 14:09:46 EST


Broken links in static deployments currently fall back to a
generic web server 404 page, which leaves users on an orphaned
error page with no direct way to continue navigating the
documentation site. Add a dedicated not-found page so deployments
can serve a project-specific 404 instead.

It keeps the normal documentation layout around the error state so
users still have the search box, table of contents, footer links,
and a clear route back to the documentation root. The penguin
logo makes it less generic and adds character to what is
otherwise a frustrating page to encounter.

For translated documentation, generate 404 pages whose return
link keeps users inside the current translation instead of always
sending them back to the English root documentation.

Actual 404 handling remains a web server concern.

Signed-off-by: Rito Rhymes <rito@xxxxxxxxxxxxxx>
Assisted-by: Codex:GPT-5.4
---
V2 adds multi-language routing support.

For 404 handling to work in nginx, point the site root at the
built documentation tree and route missing paths to the
generated 404 pages, for example:

location / {
error_page 404 /404.html;
try_files $uri $uri/ =404;
}

For translated documentation subtrees, add matching location
blocks that serve the subtree-specific 404 page, for example:

location /translations/zh_CN/ {
error_page 404 /translations/zh_CN/404.html;
try_files $uri $uri/ =404;
}

Repeat that pattern for the other translation subtrees so missing
translated pages keep users inside the current translation
instead of sending them back to the English root documentation.

I've tested the setup locally with nginx for all present translations
and the routing works fine.

These screenshots show the generated 404 page being served live in
Chrome on desktop and mobile.

Desktop screenshot:
https://github.com/user-attachments/assets/085eff0b-8661-4919-a651-6109e505ff05

Mobile screenshot:
https://github.com/user-attachments/assets/85a171f7-c2ff-483d-bc35-6719202a70e1

The screenshots above are hosted in a GitHub issue. For
convenience, anyone is welcome to post additional screenshots
there and reference them from the mailing list for discussion of
this patch:
https://github.com/ritovision/linux-kernel-docs/issues/4

diff --git a/Documentation/conf.py b/Documentation/conf.py
index 679861503..32f3aa698 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -130,6 +130,17 @@ def config_init(app, config):
"The kernel development community",
"manual"))

+ # Generate the root 404 page and per-translation copies for full-doc builds.
+ config.html_additional_pages = {"404": "404.html"}
+ if os.path.samefile(kern_doc_dir, app.srcdir):
+ translations_dir = os.path.join(kern_doc_dir, "translations")
+ for lang in sorted(os.listdir(translations_dir)):
+ full = os.path.join(translations_dir, lang)
+ if not os.path.isdir(full):
+ continue
+
+ config.html_additional_pages[f"translations/{lang}/404"] = "404.html"
+
# helper
# ------

@@ -437,6 +448,10 @@ sys.stderr.write("Using %s theme\n" % html_theme)
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["sphinx-static"]

+# Generate a simple static 404 page. Serving it for missing paths is left to
+# the web server configuration.
+html_additional_pages = {}
+
# If true, Docutils "smart quotes" will be used to convert quotes and dashes
# to typographically correct entities. However, conversion of "--" to "—"
# is not always what we want, so enable only quotes.
diff --git a/Documentation/sphinx-static/custom.css b/Documentation/sphinx-static/custom.css
index db24f4344..c4d28e1d4 100644
--- a/Documentation/sphinx-static/custom.css
+++ b/Documentation/sphinx-static/custom.css
@@ -169,3 +169,72 @@ a.manpage {
font-weight: bold;
font-family: "Courier New", Courier, monospace;
}
+
+/* Center the 404 body copy without affecting normal pages.
+ * This minimum height ensures the footer remains positioned
+ * visibly at the bottom of the page despite most of the page
+ * being empty.
+ * 100% width allows for contained centering of inner contents */
+div.kernel-404-page {
+ align-items: center;
+ box-sizing: border-box;
+ display: flex;
+ justify-content: center;
+ min-height: 90vh;
+ padding: 2rem 1rem;
+ width: 100%;
+}
+
+/* Group the content as a vertical stack */
+div.kernel-404-content {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ max-width: 28rem;
+}
+
+a.kernel-404-logo-link {
+ align-self: stretch;
+ border-bottom: none;
+}
+
+a.kernel-404-logo-link:hover {
+ border-bottom: none;
+}
+
+img.kernel-404-logo {
+ display: block;
+ height: auto;
+ margin-inline: auto;
+}
+
+div.kernel-404-content h1,
+div.kernel-404-content p {
+ margin: 0;
+}
+
+/* Make the header larger and more prominent. */
+div.kernel-404-content h1 {
+ font-size: 300%;
+}
+
+p.kernel-404-home {
+ margin-top: 0.5rem;
+ text-align: center;
+}
+
+@media screen and (max-width: 65em) {
+ /* Less viewport height because the mobile sidebar is taking
+ * up large chunk of the screen already */
+ div.kernel-404-page {
+ min-height: 40vh;
+ }
+
+ img.kernel-404-logo {
+ width: 70%;
+ }
+
+ div.kernel-404-content h1 {
+ font-size: 240%;
+ }
+}
diff --git a/Documentation/sphinx/templates/404.html b/Documentation/sphinx/templates/404.html
new file mode 100644
index 000000000..1222f3fe1
--- /dev/null
+++ b/Documentation/sphinx/templates/404.html
@@ -0,0 +1,22 @@
+{# SPDX-License-Identifier: GPL-2.0-only #}
+{% extends "layout.html" %}
+{% set path_parts = pagename.split('/') %}
+{% set home_doc = master_doc %}
+{% if path_parts|length >= 2 and path_parts[0] == 'translations' %}
+ {% set home_doc = path_parts[0] ~ '/' ~ path_parts[1] ~ '/index' %}
+{% endif %}
+{% set title = "404 Not Found" %}
+
+{% block body %}
+ <div class="kernel-404-page">
+ <div class="kernel-404-content">
+ <a class="kernel-404-logo-link" href="{{ pathto(home_doc) }}"
+ aria-label="Return home">
+ <img class="kernel-404-logo" src="{{ pathto('_static/logo.svg', 1) }}" alt="" />
+ </a>
+ <h1>404 Not Found</h1>
+ <p>The page you are searching for doesn't exist.</p>
+ <p class="kernel-404-home"><a href="{{ pathto(home_doc) }}">Return home</a></p>
+ </div>
+ </div>
+{% endblock %}
--
2.51.0