Skip to content

[Self-Hosted] Custom domain resolution broken on status-page — middleware hardcoded to stpg.dev #1968

@vetmx-tech

Description

@vetmx-tech

Description

When self-hosting OpenStatus via Docker Compose, custom domains configured
on a status page do not resolve correctly. Instead of serving the status page,
visiting the custom domain redirects to the theme editor with a 400 Bad Request
tRPC error.

Environment

  • Self-hosted via Docker Compose
  • Image: ghcr.io/openstatushq/openstatus-status-page:latest
  • Reverse proxy: Nginx
  • DNS/CDN: Cloudflare

Steps to Reproduce

  1. Deploy OpenStatus via Docker Compose using the official self-hosting guide
  2. Create a status page and set a custom domain (e.g. status.example.com)
  3. Configure DNS A record pointing to your server IP
  4. Configure Nginx to proxy status.example.comlocalhost:3003
  5. Visit status.example.com

Expected Behavior

The public status page loads at the custom domain.

Actual Behavior

The app redirects to the theme editor. The tRPC call to statusPage.get
sends slug: null because the custom domain lookup fails.

Container logs show:
{
hostnames: [ 'localhost', '3000' ],
host: 'localhost:3000',
urlHost: 'status-admin.veteranmx.com',
subdomain: 'status-admin.veteranmx.com'
}

Root Cause (Investigated)

Inspecting the compiled middleware in the status-page container:

docker exec openstatus-status-page grep -o ".\{50\}urlHost.\{50\}" \
  /app/apps/status-page/.next/server/edge/chunks/_56137d89._.js

Returns:

return console.log({hostnames:a,pathnames:s,host:n,urlHost:r.host,subdomain:o}),
a.length>2&&"www"!==a[0]&&!r.host.endsWith(".vercel.app")

The middleware derives the host from r.host (the incoming request), which
resolves to localhost:3000 internally. Additionally, the middleware contains
routing logic hardcoded to the hosted platform's stpg.dev subdomain pattern:

has:[{type:"host",value:"(?[^.]+).stpg.dev"}]

This means custom domain resolution is built around the hosted SaaS
infrastructure and does not work in a self-hosted context where r.host
will always be the container's internal address.

Setting proxy_set_header Host status.example.com and
proxy_set_header X-Forwarded-Host status.example.com in Nginx does not
resolve the issue since the edge runtime reads the actual request host,
not the forwarded header.

Workaround

Access the status page via its slug URL instead:
https://status.example.com/your-slug

And use an Nginx redirect for the root path:

location = / {
    return 301 https://status.example.com/your-slug;
}

Suggested Fix

The middleware should fall back to custom domain lookup via the database
when running in self-hosted mode (SELF_HOST=true), using
X-Forwarded-Host or a configurable STATUS_PAGE_URL environment
variable to determine which page to serve, rather than relying on
r.host or the stpg.dev subdomain pattern.

Additional Context

The custom domain UI in the dashboard also shows an A record prompt
pointing to 76.76.21.21 (the hosted platform IP), which is not
relevant for self-hosted deployments. This adds confusion during setup.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions