First Owner Setup
Updated: 2026-09-03
The first-owner setup page creates the initial Owner account after a successful fresh product installation.
Purpose
A fresh install does not require manual SQL edits to create the first usable administrator account. The supported installer prepares the database and application first; the operator then creates the initial Owner through the application.
The setup flow is intentionally narrow:
- works only when there is no active Owner account;
- creates the first Owner account;
- stores the password with
password_hash(); - requires the established user-selected password minimum of 12 characters;
- assigns the Owner role;
- logs the new Owner in;
- locks itself automatically once an active Owner exists;
- fails closed if it cannot safely determine whether an Owner already exists.
URL
/setup-owner.php
On a local install using the default web port:
http://127.0.0.1:8080/setup-owner.php
If WEB_PORT was changed in .env, use that localhost port instead.
Before using the page
For a fresh product installation, first complete:
bash tools/recipe-site-install.sh
The installer must end with:
INSTALL=PASS
That means the clean v1 database baseline has been loaded, any explicitly classified post-baseline migrations have been applied, containers are running, and product health checks passed.
Also make sure you use HTTPS if accessing the page over a network.
If the Create First Owner button does not complete setup
Original v1.0.0 browser navigation bug: The original published release emits the CSP directive upgrade-insecure-requests on every page. On a NAS that serves plain HTTP on port 8080, browsers rewrite relative links and form posts to HTTPS on that same port, causing ERR_SSL_PROTOCOL_ERROR. This can look like a dead submit button. Setting FORCE_HTTPS=0 and SECURE_COOKIES=0 alone is insufficient in the original unpatched v1.0.0. Use a working HTTPS front end or obtain the corrected code from Support until the next release. The source fix preserves secure upgrades for HTTPS sites but does not force them on intentional HTTP-only localhost setups.
Account-request dead end: If the Owner form did not finish and you subsequently used Request Account, that request remains pending without an Owner to approve it. Do not edit SQL role IDs or reset the database. Return to /setup-owner.php after correcting the transport problem; create the first Owner using a different username and leave the optional email blank if it is already claimed by the pending account. Log in as the new Owner and approve or otherwise resolve the pending request under Admin → Users.
First reload /setup-owner.php. If it says First-owner setup is already complete, the account was created; proceed to /login.php. Otherwise, verify that the installer finished with INSTALL=PASS and that you are using the intended installation URL.
The initial release sets FORCE_HTTPS=1 and SECURE_COOKIES=1 by default. These settings are intended for HTTPS. If the form is accessed using plain HTTP with a hostname, the browser can omit the secure session cookie, causing a failed CSRF token check and no Owner creation. This was reproduced on an isolated v1.0.0 install; the same install created and logged in the Owner after session-cookie configuration was corrected.
For private loopback-only initial setup, use APP_BASE_URL=http://127.0.0.1:8080 (or your actual WEB_PORT), FORCE_HTTPS=0, and SECURE_COOKIES=0 in .env. This HTTP method must only be reached directly on the server or through an SSH tunnel, never directly across a LAN or internet where the Owner password would be exposed. Then run docker compose up -d --force-recreate web from the installation folder and reload the setup page.
If accessing through an already working HTTPS reverse proxy, use its HTTPS URL in APP_BASE_URL and keep both flags set to 1. Once you switch from private HTTP bootstrap to HTTPS, restore those flags to 1 and recreate the web container.
If the form still fails, collect the browser's full URL (no passwords), the visible error/status, the result of bash tools/recipe-site-health.sh, and sanitized web container logs. Never send a password, .env, session cookie, CSRF token, or a database dump to support. Do not rerun the fresh installer or delete Docker volumes just to fix a form submission.
Expected behavior
Fresh install with no Owner
The page shows the first-owner setup form. After a valid submission it creates the Owner account and logs the new Owner in.
Existing install with active Owner
The page refuses setup and indicates that first-owner setup is already complete. It must not allow another Owner to be created through this bootstrap route.
Database/schema uncertainty
If the application cannot safely check whether an active Owner exists, setup fails closed rather than assuming it is safe to create one.
After creating the Owner
- Reload
/setup-owner.phpand confirm it refuses another first-owner setup. - Open the account menu.
- Confirm Owner/admin pages are available.
- Review Admin -> Site Settings and replace generic branding/contact defaults where appropriate.
- Create or approve additional users through the normal account/admin flow.
- Configure HTTPS/public routing if the site has not yet been exposed through its production reverse proxy/tunnel.
Existing-site check
With the default local port:
curl -s http://127.0.0.1:8080/setup-owner.php | grep -Ei 'complete|already|owner|setup' | head
An established site should indicate that first-owner setup is already complete.
Notes
The setup page is not a general user-management tool. After the first Owner exists, user creation and role management belong in the normal account request/admin flows.
Do not keep one-off owner-creation helper scripts in www/.
See also:
