Settings: error pages¶
Custom HTML pages for the errors gpm generates itself, fleet-wide or per host.
| Field | Type | Notes |
|---|---|---|
errorPages |
ErrorPagesConfig | Default custom error pages for every host (below). A ProxyHost's own errorPages overrides this. Zero value keeps gpm's built-in plain-text error output. |
In the admin UI these live in their own Error pages section (not under Settings); a host's override stays in that host's editor. The config schema is unchanged either way: the section edits
settings.errorPages.
Custom HTML pages for errors gpm itself generates: upstream unreachable
(502 connect/handshake failure, 504 a timeout awaiting the upstream), access
denied (403 from an access list, a guard middleware, or a geo rule), rate
limited (429), a dangling middleware/access-list reference (503), a dead
host, and every terminal refusal an auth middleware
writes. The upstream's own error response (its own 500, its own 404 page) is left
untouched unless its status is also listed in interceptUpstream. A status
with no matching template, and unconfigured settings/host entirely, falls
back to gpm's historical plain-text output, unchanged from before this feature.
Auth-middleware refusals covered, by gate:
| Gate | Statuses |
|---|---|
forward-auth |
401 (untrusted peer or no identity asserted), 403 (role not permitted) |
client-cert |
401 (no handshake-verified certificate), 403 (subject unmapped or role not permitted) |
auth-request |
403 (auth server said forbidden), 502 (auth server unreachable or an unexpected status) |
oidc |
403 (role not permitted), 401 (IdP returned an error, or the code exchange failed), 400 (invalid login state), 404 (request Host is not a domain of this host), 502/500 (discovery or state generation failed) |
| any auth middleware that cannot be compiled | 503 ("authentication not available") |
The per-host override applies to these exactly as it does to an access-list
denial: the host's own errorPages is resolved first, then the settings-level
pages, then the plain-text default.
Two things are deliberately not error pages:
- Redirects into a sign-in flow. The OIDC gate's
302to the IdP and theauth-requestgate's302into the outpost sign-in are flows, not errors. - Anything the identity provider itself served. In
auth-requestmode the IdP owns its sign-in, callback and sign-out endpoints, and gpm proxies those responses verbatim. The IdP response always wins there: gpm never overwrites it with an error page, because that page would replace a working login screen.
| Field | Type | Notes |
|---|---|---|
dir |
string | Directory of html/template files named <status>.html (e.g. 502.html), plus an optional default.html fallback used when no status-specific template matches. Relative to the managed cert store, confined exactly like a custom certificate's files: no absolute path, no ... |
inline |
map[string]string | Status code (as a string, e.g. "502"), or the literal "default", mapped directly to html/template source, for a handful of pages an operator would rather keep in config than mount a directory. |
interceptUpstream |
[]int | Status codes (4xx/5xx) for which the upstream's own error response body is also replaced by the configured page. Default: only gpm-generated errors are replaced, never the upstream's own. |
Templates execute with {{.Status}} (int), {{.StatusText}} (e.g. "Bad
Gateway"), {{.Host}} (the matched ProxyHost name), and {{.RequestID}} (the
X-GPM-Request-Id value when -debug-headers/GPM_DEBUG_HEADERS=1 is on,
empty otherwise), html/template, so all four are contextually escaped. A
ProxyHost's own errorPages is resolved first for a given status (falling
back to its own default template, then to the settings-level pages) so a host
override always wins; a host with no override of its own uses the
settings-level pages outright.
When a broken template is caught:
| Moment | Behaviour |
|---|---|
| Settings write | An inline template that does not parse is rejected, so it never commits. |
| Config reload | A parse error (or an unreadable dir) fails the reload with a clear message; the previously compiled pages stay installed. |
| Startup | A dir template that broke out of band is logged as a warning and gpm starts with its built-in error output; it never refuses to boot over an error page. |
A rendered page carries X-Content-Type-Options: nosniff, matching the
built-in plain-text response, so configuring a page never drops a header the
default one had.
# settings.yaml
errorPages:
dir: errorpages # relative to GPM_CERT_DIR
interceptUpstream: [502, 503]
# a proxy host overriding just its own 429 page
errorPages:
inline:
"429": |
<html><body><h1>Slow down</h1><p>{{.Host}} is rate-limiting you.</p></body></html>