Troubleshooting

curl: (6) Could not resolve host: <name>.portzero.local

The overlay isn’t active. .portzero.local names are only visible once the overlay is running, which requires the daemon to be started with root / elevated privileges. Start it with sudo:

sudo -E portzero start --foreground

If you started the daemon unprivileged it logged continuing in cloud/local-only mode and the overlay is inactive. See Platform privileges.

My process was never discovered

PZ_TUNNEL must be set before the process starts — the daemon reads the frozen environment captured at process start; a value set after launch (in os.environ / process.env / os.Setenv) is invisible to it.

  • Verify before launching: echo $PZ_TUNNEL.
  • Use direnv, or export the variable in the same shell that execs the process.
  • Confirm the value is a full domain with a recognized suffix (.portzero.local or *.<username>.tunnel.portzero.cloud); nothing is appended implicitly. See Tunnels & PZ_TUNNEL.

macOS: a process run under a system interpreter is never discovered

On recent macOS the kernel hides the environment of SIP-protected system binaries from every other process — including the daemon, and including root. So if you tag a process whose executable is a system-shipped interpreter, its PZ_TUNNEL is invisible and the process is silently not discovered. Affected executables include:

  • /usr/bin/python3 (the Xcode/Command-Line-Tools python)
  • system /usr/bin/perl, /usr/bin/ruby
  • /bin/sh, /bin/bash (the system copies)

Run the tagged process under a non-system runtime instead — anything not under /usr/bin or /bin//sbin is fine: Homebrew, nvm, pyenv, rbenv, asdf, or a user-compiled binary. Quick check: PZ_TUNNEL is readable iff ps -p <pid> -wwwE -o command= | tr ' ' '\n' | grep PZ_TUNNEL prints it. If that shows nothing, the process is under a SIP binary — switch runtimes.

Linux and Windows have no such restriction, so system interpreters are discovered there normally.

The literal {branch} appears in status

Template resolution failed. For native processes, ensure you’re on a real git branch (not detached HEAD) within the repo. For containers, the daemon resolves {branch}/{worktree} from bind mounts / compose labels — if discovery of those fails, the placeholder is left literal. See Tunnels & PZ_TUNNEL.

Wrong path taken (cloud vs. local)

The suffix decides the target, not any flag:

  • .portzero.local → Local overlay
  • *.<username>.tunnel.portzero.cloud → Cloud tunnel

Double-check the suffix in your PZ_TUNNEL value.

Virtual interface creation fails even with sudo (Linux)

Ensure /dev/net/tun exists and the tun module is loaded (modprobe tun). Inside containers, the device must be made available (--device /dev/net/tun --cap-add NET_ADMIN).

DNS resolves but the connection hangs / refuses

The name resolved to a virtual IP but the proxy couldn’t reach the backend. Check that the backend process or Docker container is still listening on its ephemeral port and that portzero status shows the expected real address. Restarting it re-registers the same stable virtual IP.

.portzero.local names don’t resolve in a cloud sandbox or container

The overlay (virtual interface + embedded DNS server) can be up and healthy while *.portzero.local still doesn’t resolve, if the OS-level scoped resolver never got wired up. On Linux that wiring needs either systemd-resolved or a dnsmasq fallback; container/cloud sandboxes — including remote coding-agent sessions — frequently have neither. This step logs a warning and does nothing further; it’s best-effort and non-fatal by design, and doesn’t affect the overlay itself.

Fix: add the dashboard/tunnel hosts entries yourself (portzero setup suggests the exact line; portzero doctor re-checks it), or skip name resolution and use portzero url / portzero env / portzero wait to get the concrete tunnel URL for scripts — see CI and scripts, which uses the same pattern because CI runners have the identical no-systemd shape.

Before editing /etc/hosts yourself, know that a plain permission check isn’t enough to tell whether an edit will actually work: a Linux immutable file attribute rejects writes with a bare “permission denied” that looks like a normal permissions problem; some distros symlink /etc/hosts to a generated, declaratively-rebuilt path where a direct edit is pointless; and inside an actual container, /etc/hosts is typically its own bind mount that the runtime overwrites on every restart, so an edit “succeeds” but doesn’t survive one. portzero setup/portzero doctor check for all three before writing or recommending a write, rather than assuming from indirect signals.

portzero login hangs in a remote/headless session

Older versions of portzero login’s default flow bound a local TCP listener and waited for a browser page’s JavaScript to post credentials back to it. In a remote/headless session there’s no local browser to open, and opening the printed URL from your own laptop’s browser didn’t help either — the dashboard page ran in your laptop’s browser, whose 127.0.0.1 was your laptop’s loopback, not the sandbox’s.

This is fixed: the printed URL now carries a session code instead of relying on a local callback port. The CLI polls the API in the background and picks up the completed login automatically, so nothing needs to be typed back into the sandbox at all. The URL can be opened on any device. portzero login --interactive (email one-time code, no browser or network callback of any kind) also works, if you’d rather not open a browser at all. See FAQ for the full remote-session setup.

Brave shows ERR_CERT_AUTHORITY_INVALID for a *.portzero.local name

If this only happens in the Snap package of Brave, use the native Brave package instead. Port Zero installs its local CA into the Linux system trust store and browser NSS stores, but some Snap Brave/Chromium builds still ignore those local trust anchors and report the Port Zero CA as an unknown issuer. Non-Snap Brave uses the installed CA correctly. See Known limitations for the full writeup (and other browser/engine CA-trust gaps).

Seeing the daemon’s own logs

  • ~/.portzero/daemon/daemon.log (background mode).
  • Or run --foreground to see logs on the console.
  • portzero status shows the currently discovered Local tunnels and routes.