proxy
The Manager renders the edge proxy's configuration and pushes it to the proxy. pfy proxy lets you look at that loop and drive it by hand: status reports its state, diff compares what the Manager renders now with what the proxy applied, and sync pushes immediately. All three need a system-administrator token.
Synopsis
pfy proxy status [--exit-code]
pfy proxy diff [<app-slug> | --slug <slug> | --id <uuid>] [--project <slug>] [--show-rendered] [--exit-code]
pfy proxy sync [--exit-code]All three require a system-administrator token: they describe, and sync reshapes, the install's edge rather than the caller's own data. A non-admin token does not get an error; see Withheld looks like off.
The model
The Manager renders the edge proxy's whole configuration from its deployment data and pushes it to the proxy's admin API. It does this on its own after every config-changing mutation and on a periodic reconcile; these commands let you look at that loop and drive it by hand.
Two hashes carry the state:
- the rendered hash: SHA-256 of the config the Manager renders right now
- the applied hash: the config the proxy is running
Everything below turns on where the applied hash came from, because the Manager can fill it in two very different ways:
proxy_metrics_url | Applied hash source | What it means |
|---|---|---|
| configured | proxy | Read back from the proxy's own /config-hash. An observation. |
| not configured | push | The hash the Manager itself just rendered, recorded after the proxy returned 2xx. An assumption. |
With source push, rendered == applied is the same number written down twice. It is an identity, not evidence, and it stays true even if the proxy failed to load the config, was rolled back, was restarted onto a stale on-disk Caddyfile, or was reconfigured by hand.
Matching hashes are not, by themselves, a check.
pfy proxy status reports an unverified match as unknown, not healthy; the one branch that says healthy is the only one holding evidence. Set proxy_metrics_url in the Manager to turn the comparison into a real check.
pfy proxy status
Reads the sync loop's telemetry and scores it.
pfy proxy statusA verified, converged install:
Proxy sync is ON and healthy - the proxy itself reports running the config the Manager renders.
sync: ON
verdict: healthy
drift: in_sync
rendered hash: 9f2c4ae1b7d03e58
applied hash: 9f2c4ae1b7d03e58
hash source: the proxy itself (verified)
last attempt: 2026-09-05T14:31:07ZThe same hashes, with no proxy_metrics_url:
Proxy sync is ON and the hashes match, but NOTHING VERIFIED IT - the proxy has not been asked what it is running.
the rendered and applied hashes match, but the applied hash is the
Manager's own record of what it last pushed (source "push"), not something
read back from the proxy. Accepting a config and serving it are different
claims: if the proxy failed to load it, was rolled back, or was changed
out of band, this payload would look exactly the same. Configure
`proxy_metrics_url` in the Manager so the applied hash is read from the
proxy's own /config-hash, and this becomes a real check.
sync: ON
verdict: unknown
drift: in_sync
rendered hash: 9f2c4ae1b7d03e58
applied hash: 9f2c4ae1b7d03e58
hash source: the Manager's own last push (ASSUMED, not verified - set proxy_metrics_url to verify)
last attempt: 2026-09-05T14:31:07ZNote drift: in_sync alongside verdict: unknown. The drift field reports the hash comparison, and the hashes genuinely do match; the verdict reports whether that means anything.
A render the proxy rejected:
Proxy sync is ON but UNHEALTHY - the Manager could not render or could not push the proxy's configuration.
sync: ON
verdict: unhealthy
drift: drift
rendered hash: 9f2c4ae1b7d03e58
applied hash: 41b8005cd9e7a222
hash source: the proxy itself (verified)
last attempt: 2026-09-05T14:33:52Z
last error: proxy rejected the configuration: adapting config using caddyfile: Caddyfile:212 - unrecognized directiveThe previous routing is still serving: the proxy rejects a bad config whole rather than applying part of it. The applied hash is the older config it kept.
Exit codes
Only with --exit-code; without it a successful read always exits 0.
| Code | Meaning |
|---|---|
0 | sync is on, the last push was accepted, rendered == applied, and that agreement was verified |
1 | the status could not be read at all: unreachable manager, bad token, bad flags |
3 | sync is on and the state is bad: unhealthy, or drifted |
4 | the state could not be determined: sync off, status withheld, no applied hash yet, or the hashes match but nothing verified them |
pfy proxy diff
Compares the two hashes and reports in_sync / drift / unknown.
pfy proxy diffin_sync - the proxy last applied the same config the Manager renders now.
compared: SHA-256 hashes only (the proxy does not expose its running config text)
rendered hash: 9f2c4ae1b7d03e58
applied hash: 9f2c4ae1b7d03e58DRIFT - the config the Manager renders now differs from the one the proxy last applied.
compared: SHA-256 hashes only (the proxy does not expose its running config text)
rendered hash: 9f2c4ae1b7d03e58
applied hash: 41b8005cd9e7a222This is not a line-by-line diff.
No Manager API exposes the proxy's running configuration text, only its hash. pfy proxy diff therefore tells you that the two differ, never how. The name is aspirational.
diff is narrower than status, and scores an unverified match as clean
diff's contract is explicitly the hash comparison, so on an unverified match it reports in_sync and exits 0, where pfy proxy status reports unknown and exits 4 on the identical payload.
That is deliberate (the hashes do match, and reporting the comparison is all diff claims to do), but it means pfy proxy diff --exit-code is not a gate on the edge being correct. Use pfy proxy status --exit-code for that.
Inspecting one application's rendered vhost
Given an application target, diff additionally prints the vhost blocks the Manager would render for it:
pfy proxy diff my-app --project my-saas
pfy proxy diff my-app --project my-saas --show-rendered # full textThis is the rendered side only. It is useful to read, it is not diffed against anything, and it never changes the verdict above it.
Flags
| Flag | Description |
|---|---|
--slug | target slug (or pass it positionally); a UUID value is auto-detected as an ID |
--id | target by ID explicitly, the escape hatch when a slug happens to look like a UUID |
--project | project slug, needed to resolve an application slug to an ID |
--show-rendered | with an application target, print the full rendered vhost text |
--exit-code | exit 3 on drift and 4 when undetermined, instead of always 0 |
Exit codes
| Code | Meaning |
|---|---|
0 | in_sync: rendered and applied hashes match |
1 | the comparison could not be made at all |
3 | drift: the hashes differ |
4 | unknown: sync is off, status withheld, or nothing is known to have been applied |
pfy proxy sync
Asks the Manager to render and push now, blocking until the push has settled.
This is the same push the Manager already performs on its own. Run it by hand when you would rather not wait for the reconcile, or to re-assert the declared config over a proxy that was changed out of band.
There is deliberately no confirmation prompt and no --yes. This pushes the configuration the Manager already declares: it is convergent and idempotent, it deletes nothing, and a config that does not parse is rejected whole with the previous routing still serving.
Outcomes
A call that returned without an error is not proof that this Manager pushed, so the outcome is always stated:
| Outcome | Meaning |
|---|---|
PUSHED | the proxy accepted the configuration; it is live |
ALREADY_CURRENT | the proxy reported it is already running exactly this configuration, so nothing was pushed. This one is read from the proxy, so it is evidence about the edge |
LOCK_BUSY | another Manager instance holds the push lock. This Manager pushed nothing. The install is converging, just not from here; re-run to sync from this Manager |
Sync being off, a failed render, a config the proxy rejected, and an unreachable proxy are errors rather than outcomes: they never come back as a result.
PUSHED - the Manager rendered the current configuration and the proxy accepted it.
manager: the proxy accepted the configuration
The sync loop's state after this push:
Proxy sync is ON and healthy - the proxy itself reports running the config the Manager renders.
sync: ON
verdict: healthy
drift: in_sync
rendered hash: 9f2c4ae1b7d03e58
applied hash: 9f2c4ae1b7d03e58
hash source: the proxy itself (verified)
last attempt: 2026-09-05T14:31:07ZLOCK_BUSY exits 4 with or without --exit-code, even when the state it reports looks healthy:
NOT SYNCED (LOCK_BUSY) - this Manager pushed nothing: another Manager instance holds the proxy-sync lock.
manager: another Manager instance holds the push lock
the install is converging, just not from here: another instance is pushing
from the same database. The state below was read by THIS Manager and may
predate that other push. Nothing you asked for was done by this call -
re-run it to push from this Manager.A drift report is information about the world; a sync that did not sync is the command failing to do the one thing it was asked to do.
Exit codes
| Code | Meaning |
|---|---|
0 | the call pushed, or found the proxy already current, and (with --exit-code) the state it left behind is healthy, in sync, and verified |
1 | the sync did not happen: sync is off, the render failed, the proxy rejected the config or was unreachable, or the token is not an admin token |
3 | only with --exit-code: the push completed and the state it left behind is bad |
4 | LOCK_BUSY (not gated by the flag), or, with --exit-code, a state that cannot be determined, including a PUSHED whose agreement is unverified |
Withheld looks like off
When the token is not a system-administrator one, the Manager does not return an error. It returns proxy status in the same zero-valued shape it uses when no sync loop is configured, and the two cannot be told apart from the client side:
Proxy sync is OFF - the Manager is not maintaining the proxy's configuration.
sync is OFF or was withheld: the Manager's proxy status is unset either
because `proxy_admin_url` is not configured (no sync loop runs, and the
proxy holds whatever config last reached it) or because this token is not
an admin token (the Manager withholds proxy status using the same
zero-valued shape). This is NOT a clean result - nothing has been
compared.Both are reported as unknown and exit 4 with --exit-code. Neither is ever scored as healthy: in that state the Manager has never looked at the proxy, so its empty hashes and false booleans are zero values, not agreement.
In CI
# Gate a pipeline on the edge actually being correct.
pfy proxy status --exit-codeReserve exit 1 in your own handling for "could not get an answer": it is the default for every pfy failure and does not describe the edge.
On an install without proxy_metrics_url, pfy proxy status --exit-code can never exit 0: nothing is verifiable, so the answer is always "undetermined". If a gate on such an install is what you want, configure proxy_metrics_url first; do not substitute pfy proxy diff --exit-code, which returns 0 by ignoring the question.
See also
- Proxy architecture: what the rendered configuration contains
- Deployment: the proxy: configuring
proxy_admin_urlandproxy_metrics_url deploy diff: the unrelated three-way drift check for application deployments