Skip to main content

Set up the first administrator

A new broker has one organization and no way to sign in. The command that creates the first administrator, pb admin bootstrap or pb cloudflare setup, issues a one-time setup token. You spend that token once, either in the browser or with two HTTP calls, to choose a password and connect an authenticator app. After that you sign in with your e-mail address, your password and a six digit code.

What you need​

  • The address of the broker, for example https://broker.example.com. This is the public address the broker was configured with. Requests from any other origin are refused.
  • The setup token. It is delivered separately from this page: printed once by the command, or written to the file you named with --token-file. Treat it like a password.
  • An authenticator app that supports time based codes (6 digits, 30 seconds, SHA-1), or any tool that can compute them from a base32 key.

The token expires after 24 hours and works once. If it expires or is lost before setup finishes, request a fresh one through administrator bootstrap, using an unused output path if you use --token-file. With pb cloudflare setup, request a replacement explicitly with --refresh-setup-token. Issuing a new token invalidates the old one. After setup finishes, sign in with the enrolled credentials. Bootstrap cannot reset that login; use the intentional reset described in After setup if needed.

In the browser​

  1. Open <broker address>/_pb/ui/setup. The address contains no token.
  2. Paste the setup token and continue.
  3. Scan the QR code with your authenticator app. If you cannot scan it, type the setup key shown below the code into the app. The key is the same secret the QR code carries.
  4. Choose a password, repeat it, enter the current six digit code and finish.
  5. The page confirms that setup is complete and offers a button to open the broker. You are already signed in.

The token is never part of the address, so it does not reach browser history, server logs or a Referer header. The page keeps the token, password, setup key and code in memory only: nothing is written to browser storage, nothing is copied to the clipboard, and everything is cleared when setup finishes or you leave the page. The QR code is drawn in your browser; no external service sees the key.

If the broker gives no clear answer to the final step, the page says so and does not retry on its own. Keep the password and authenticator enrollment, then open <broker address>/_pb/ui and sign in, or use the local-login API described below. If sign-in is refused and setup did not complete, return to setup with the unused token, or request a replacement as described in What you need if it expired or was lost. If sign-in fails after setup completed, follow After setup to arrange an intentional local-login reset.

A token that is wrong, expired or already used gets the same generic refusal. The page does not say which, and it never reveals whether an account exists.

With curl​

Use this when no browser is available. Send both calls from a machine you trust, with the broker's own address in the Origin header and a JSON content type. The routes are the same ones the browser page uses.

Run this from a private terminal on a trusted machine. Do not use shell tracing, terminal recording or a CI job to enroll credentials. Create a private temporary directory first; the restrictive umask applies to request bodies, responses and the cookie jar. Keep the same shell open for the following steps:

set +x
umask 077
setup_dir=$(mktemp -d)
trap 'rm -rf "$setup_dir"' EXIT HUP INT TERM

Use a trusted local editor to create $setup_dir/start.json containing {"token":"<setup token>"}. Read the token from its separate delivery, rather than putting it in the command line or shell history. Start setup; only the HTTP status is printed:

curl --silent --show-error --fail \
--header 'Content-Type: application/json' \
--header "Origin: $BROKER" \
--data-binary "@$setup_dir/start.json" \
--output "$setup_dir/start-response.json" \
--write-out '%{http_code}\n' \
"$BROKER/_pb/auth/local/setup/start"

A 200 response file contains provisioningUri and secret. Open it in a trusted local viewer that does not upload, log, back up or retain the content. Enter secret in your authenticator, or render provisioningUri locally. Do not print this file to the terminal or paste it into a ticket, chat or CI log.

Create $setup_dir/finish.json in the private editor with {"token":"<setup token>","password":"<password>","code":"<six digit code>"}. Finish setup, saving the response and session cookies privately:

curl --silent --show-error --fail \
--header 'Content-Type: application/json' \
--header "Origin: $BROKER" \
--data-binary "@$setup_dir/finish.json" \
--output "$setup_dir/finish-response.json" \
--cookie-jar "$setup_dir/cookies" \
--write-out '%{http_code}\n' \
"$BROKER/_pb/auth/local/setup/finish"

Success is 204; the cookie jar holds the session for subsequent curl requests with --cookie "$setup_dir/cookies". A weak password returns 400, and a refused token or code returns 401. Do not retry setup automatically after a connection failure: it may have finished. Use the sign-in call below with a fresh code instead. Curl cookies belong to that client; opening the ordinary UI in a separate browser does not transfer that session.

To sign in after an uncertain finish, create $setup_dir/login.json privately with {"email":"<administrator e-mail address>","password":"<chosen password>","code":"<fresh six digit code>"}. Use the same origin and protected response handling:

curl --silent --show-error --fail \
--header 'Content-Type: application/json' \
--header "Origin: $BROKER" \
--data-binary "@$setup_dir/login.json" \
--output "$setup_dir/login-response.json" \
--cookie-jar "$setup_dir/cookies" \
--write-out '%{http_code}\n' \
"$BROKER/_pb/auth/local/login"

A 204 confirms sign-in. If sign-in is refused and setup did not complete, resume setup with the unused token or request a replacement as described in What you need. If it is refused after setup completed, use the intentional reset described in After setup. A new bootstrap token cannot reset an enrolled account. When finished, close the viewer and editor without retaining credential-bearing backups and remove all request, response and cookie files. The exit trap also removes the private directory:

rm -rf "$setup_dir"

After setup​

Later sign-ins use your e-mail address, password and a current code, sent to POST /_pb/auth/local/login. A setup token cannot be reused, and it never resets an account that has finished setup. To reset a local login on purpose, select the database explicitly and write the replacement setup token to an unused protected file. Supply ADMIN_EMAIL as the administrator's e-mail address and SETUP_TOKEN_FILE as that output path.

For SQLite, set SQLITE_FILE to the deployment's database file:

set +x
umask 077
pb admin reset-local --email "${ADMIN_EMAIL:?}" --sqlite "${SQLITE_FILE:?}" \
--token-file "${SETUP_TOKEN_FILE:?}"

For Workers, set D1_ID to the database ID and supply CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN through the protected operator environment:

set +x
umask 077
pb admin reset-local --email "${ADMIN_EMAIL:?}" --cloudflare \
--database-id "${D1_ID:?}" --token-file "${SETUP_TOKEN_FILE:?}"

Use the replacement token to repeat setup and enroll the new local-login credentials.

Cleanup inventory​

Cloudflare inventory schema 2 records uploaded version history, plain binding values grouped by binding name, and the R2 lifecycle baseline observed during provisioning. Older inventory schemas are refused; preserve the original private inventory for explicit reconciliation rather than changing its version by hand. Deployment refuses another upload before exceeding 10,000 recorded versions or 64 distinct plain binding values. Historical values remain necessary to verify inactive Worker versions during cleanup.

Teardown validates ownership and attachments before mutations, removes recorded routes, domains, queue consumers and schedules, then drains R2 before deleting Workers, queues and databases. If an attachment cannot be removed, the drain does not start. A later failure can leave triggers removed; retain the inventory and rerun cleanup. Persistent bucket conflicts after an empty drain report possible unrecorded multipart uploads, which require inspection with S3 credentials.

Adopted D1 databases and R2 buckets always require --i-understand-this-deletes, including test-run inventories. Production teardown also requires --inventory-id equal to identity.owner_digest. Janitor retains adopted data resources for explicit teardown. An incorrect inventory ID exits 2; uncertain or incompletely confirmed object deletion exits 6. Janitor skips foreign-account inventories and unrelated JSON files, and shares account Worker reference discovery across the selected expired inventories in one run. It checks selected Workers live and only reads foreign Workers to verify references.

The provision API's R2 jurisdiction query parameter still requires live verification against cf-r2-jurisdiction. Fixture tests do not prove that the provider honors the query parameter. Maintainers must also verify notification discovery in the default, EU and FedRAMP jurisdictions with the target token.

Tracing​

Workers automatic tracing is opt-in. To enable it, add traces to the existing Cloudflare observability port in broker.config.ts, then rebuild and deploy:

platformPorts: {
cloudflare: {
// Keep the other configured ports here.
observability: {
adapter: 'workers',
deploymentIdEnv: 'DEPLOYMENT_ID',
traces: {
enabled: true,
headSamplingRate: 0.05,
destinations: ['my-otlp-destination'],
persist: false,
},
},
},
},

Omitting traces uploads no tracing settings; Cloudflare's default is off. Only enabled is required when configuring traces. Start with a low sampling rate such as 0.05 (5%) and adjust for traffic and diagnostic needs. The rate must be greater than zero and at most one; Cloudflare defaults to one when it is omitted. destinations optionally names one to ten unique OTLP destinations configured in the Cloudflare dashboard. persist: false disables Cloudflare trace persistence; its default is true. For billing from 2026-12-01, see Cloudflare observability pricing.

Cloudflare automatically instruments handler calls, outbound fetches, binding calls and RPC without an SDK. Automatic fetch spans can record the full outbound URL, query string included. Signed or token-bearing upstream URLs, including private registry URLs and npm tarballs behind signed URLs, can end up in traces and in any OTLP destination. Operators who export traces to a third party should check what the fetch spans keep.

The broker's own OTLP spans, including traceparent propagation and upstream.fetch, are independent of this setting. Their endpoint and header environment references remain configured through endpointEnv and headersEnv.