Custom domain setup fails with "Not authorized" on self-hosted instances
Description
When trying to set up a custom domain for an organization on a self-hosted Cap instance, the operation fails with a 500 Internal Server Error and the message "Not authorized".
The custom domain dialog works fine (UI loads, input is enabled, the "Next" button is clickable), but clicking "Next" triggers a Server Action (POST /dashboard/settings/organization) that returns a 500 error.
Steps to reproduce
- Deploy Cap using the official
docker-compose.coolify.yml or docker-compose.yml with the pre-built image ghcr.io/capsoftware/cap-web:latest
- Log in and go to Organization Settings
- Click Setup on the Custom Domain section
- Enter a valid domain (e.g.
share.example.com)
- Click Next
Expected behavior
The domain should be saved and the setup should proceed to the verification step.
Actual behavior
The request fails with:
POST https://cap.example.com/dashboard/settings/organization 500 (Internal Server Error)
Server logs show:
Root cause analysis
After investigating the source code, the issue appears to be related to two things:
-
NEXT_PUBLIC_IS_CAP is not set in the pre-built Docker image. The proxy middleware in apps/web/proxy.ts activates restrictive logic when buildEnv.NEXT_PUBLIC_IS_CAP !== "true". While /dashboard is in the allowlist, the Server Action context may still be affected by the middleware behavior on self-hosted builds.
-
The getCurrentUser() call in apps/web/actions/organization/update-domain.ts returns null for the Server Action, even though the same session works fine for page rendering. This suggests the session is not being properly passed or validated in the Server Action context on self-hosted builds.
Environment
- Cap version:
ghcr.io/capsoftware/cap-web:latest (as of July 2026)
- Deployment: Docker Compose on VPS (Dokploy)
NEXT_PUBLIC_IS_CAP: not set (default in pre-built image)
WEB_URL and NEXTAUTH_URL: correctly configured with production domain
NEXTAUTH_SECRET: properly generated with openssl rand -hex 32
Suggested fix
Either:
- Set
NEXT_PUBLIC_IS_CAP=true in the pre-built Docker image so self-hosted instances behave like cap.so
- Or ensure Server Actions properly pass the session on self-hosted builds where
NEXT_PUBLIC_IS_CAP is unset
- Or document that custom domains require building from source with
NEXT_PUBLIC_IS_CAP=true
Related
Custom domain setup fails with "Not authorized" on self-hosted instances
Description
When trying to set up a custom domain for an organization on a self-hosted Cap instance, the operation fails with a
500 Internal Server Errorand the message"Not authorized".The custom domain dialog works fine (UI loads, input is enabled, the "Next" button is clickable), but clicking "Next" triggers a Server Action (
POST /dashboard/settings/organization) that returns a 500 error.Steps to reproduce
docker-compose.coolify.ymlordocker-compose.ymlwith the pre-built imageghcr.io/capsoftware/cap-web:latestshare.example.com)Expected behavior
The domain should be saved and the setup should proceed to the verification step.
Actual behavior
The request fails with:
Server logs show:
Root cause analysis
After investigating the source code, the issue appears to be related to two things:
NEXT_PUBLIC_IS_CAPis not set in the pre-built Docker image. The proxy middleware inapps/web/proxy.tsactivates restrictive logic whenbuildEnv.NEXT_PUBLIC_IS_CAP !== "true". While/dashboardis in the allowlist, the Server Action context may still be affected by the middleware behavior on self-hosted builds.The
getCurrentUser()call inapps/web/actions/organization/update-domain.tsreturns null for the Server Action, even though the same session works fine for page rendering. This suggests the session is not being properly passed or validated in the Server Action context on self-hosted builds.Environment
ghcr.io/capsoftware/cap-web:latest(as of July 2026)NEXT_PUBLIC_IS_CAP: not set (default in pre-built image)WEB_URLandNEXTAUTH_URL: correctly configured with production domainNEXTAUTH_SECRET: properly generated withopenssl rand -hex 32Suggested fix
Either:
NEXT_PUBLIC_IS_CAP=truein the pre-built Docker image so self-hosted instances behave like cap.soNEXT_PUBLIC_IS_CAPis unsetNEXT_PUBLIC_IS_CAP=trueRelated