Moving an installation to another address
An installation’s public URL (VARLATCH_PUBLIC_URL) is the address people
open in the browser. Use varlatch move to change it: for a new domain, or
to go from a tailnet address to a public one.
What a move changes
Section titled “What a move changes”Passkeys belong to an address: the browser registers each one for the host of the public URL (the WebAuthn relying party) and never offers it anywhere else. So a move is a re-enrollment event:
- Every passkey stops working, and every browser session ends. The move removes the passkeys of the old address, so nothing mistakes them for working ones.
- Each person enrolls a new passkey on the same identity, with a one-time link the move prints. Their access, memberships, grants, and audit history stay as they are.
- Passkeys and the dashboard stop working at the old address. With the public or tailnet ingress it stops answering. With the external ingress, your own proxy decides.
What keeps working:
- CLI, agent, and machine credentials. Their tokens don’t name an
address. Change the server address where it is configured:
serverin eachvarlatch.toml,VARLATCH_SERVER, and--serverin CI jobs. People sign in again withvarlatch login --server <new address>. - OIDC bindings. They match the external identity provider’s issuer and
audience, not this address. Only the jobs’
--serverchanges. - Sync Targets and webhooks. They store where Varlatch sends values and events, not this address.
Two things need a decision before you move:
- Tailnet Constraints, when you leave the tailnet ingress. Without it,
nothing reaches the tailnet listener, so the access they guard is denied
until you remove them (
varlatch tailnet requirements, thenvarlatch tailnet remove <id>). - Open invitations. Their links keep working, but they name the old
address. Send people the same link with the new address in front of
/enroll#.
Before you start
Section titled “Before you start”- The new address works: for the public ingress, its DNS record points at this host and ports 80 and 443 are reachable, as in the deployment guide.
- The backup key (or its passphrase file) and the Root KEK file, as for an upgrade: the move takes an archive first and verifies it.
- A way to send each person their link that you trust, and a word to them that their passkey will stop working.
From the Compose directory, on the host:
varlatch move --public-url https://vault.example.org \ --bek-file /path/to/backup-key --kek-file /path/to/varlatch-kekAdd --ingress public or --ingress external when the ingress changes too,
for example when you leave the tailnet. The command:
- Reads what the move affects and prints it: the number of people,
passkeys, and sessions, and any Tailnet Constraints or open invitations.
It asks before it changes anything (
--yesskips the question). - Takes an archive in the Compose directory’s
backups/and verifies it against the release the installation runs. Restoring it is the way back. - Writes the new address to
varlatch-install.jsonand.env, and starts the installation with it. With the public ingress, it waits for the certificate. - Gives the Application Plane (Convex) the new token issuer.
- Removes the passkeys of the old address, ends every browser session, and revokes the dashboard’s tokens.
- Prints one link per person, Installation Admins first, and waits until an Installation Admin has enrolled a passkey. Then it checks the installation’s health, as setup does.
Open your own link first. Then send each person theirs. A link works once,
for that person only, and expires after 24 hours (--reenroll-hours, up to
168).
Need new links, for someone who missed theirs or for a person who lost a passkey later:
varlatch admin reenroll --identity <identity-id> # or --allA new link for a person revokes the unused one before it.
If the move stops
Section titled “If the move stops”Run varlatch move again, without --public-url. varlatch-move.json
records how far the move got, so a rerun takes no second archive, issues no
new links, and never removes passkeys people already enrolled at the new
address. The server also records the address of the last move, and refuses
to remove passkeys a second time. varlatch setup refuses to run while a
move is open.
If varlatch-move.json itself is damaged, every command refuses to guess.
varlatchd admin move-facts (in the varlatchd container) shows the address
varlatchd runs at and how many passkeys exist: zero means the old ones were
removed. Write the file back with from, to, both ingresses, and the
archive’s path, then run varlatch move again.
Going back
Section titled “Going back”Give the move up:
varlatch move --abandonIt puts the old address back in varlatch-install.json and .env and
closes the move. It says whether the old passkeys were already removed:
- Not removed yet (the move stopped before step 5): run
varlatch setup, and the installation is back at its old address. - Removed: restore the archive the move took (it prints the path, see
Backup and recovery), then run
varlatch setup. Passkeys enrolled at the new address are not in that archive, and would not work at the old address anyway.
Afterwards
Section titled “Afterwards”- Point the old DNS name elsewhere, or remove it.
- Leaving the tailnet: remove the old node in the Tailscale admin console.
Its state stays in the
tailscale-statevolume until you remove it. - With the public ingress, the old address’s certificate stays in Caddy’s data volume, unused.
Limits
Section titled “Limits”- Moving to the tailnet ingress is not supported yet. The Tailscale node keeps its own name and state. Set up a new installation on the tailnet and restore an archive into it instead.
- Changing only the ingress, at the same address, is not supported yet. Every passkey would keep working, so it is not a move.
- Coolify installations have no
varlatch-install.json, so there is novarlatch move. Do the same steps by hand: take and verify an archive; changeVARLATCH_PUBLIC_URLin Coolify and redeploy (each deploy runsconvex-deploy, which gives Convex the new issuer); then run, in thevarlatchdcontainer,node dist/cli.js admin public-url-changed --from <old address>once (a second run is refused) andnode dist/cli.js admin reenroll --all.

