ztna CLI
The ztna command is QuickZTNA's device agent and admin tool.
Every device in your tailnet runs it — as a background service for the VPN,
and as an interactive CLI for status, diagnostics, and policy operations.
39 commands across 14 categories. Every command below is implemented in the
current release; no aspirational documentation.
curl -fsSL https://login.quickztna.com/install.sh | sh (Linux/macOS) or irm https://login.quickztna.com/install.ps1 | iex (Windows). Pre-auth rollout: add ZTNA_AUTH_KEY=tskey-auth-xxx to the environment.
Global environment + config
Every command respects these environment variables and reads/writes these files:
Pre-auth key for headless login. When set, overrides all other auth methods. Used by ztna login and ztna up.
Log level: debug, info, warn, error. Overridden by --log-level flag if present.
API URL, org ID, hostname, DNS, route config.
Machine ID, tailnet IP, WireGuard public key, DERP auth token.
JWT access + refresh tokens.
Daemon log (rotating, 10MB max, 2 backups).
Saved connection profiles for multi-org users.
Daemon PID (separate PID for userspace mode).
ztna up automatically falls back to userspace mode (no TUN device, no root required). TUN mode is only attempted if elevated. Log file location shifts to %ProgramData%\QuickZTNA\ztna.log on Windows.
Authentication & session
ztna login
Authenticate with the QuickZTNA control plane. Four supported methods: browser (default), auth key (headless), OAuth, interactive email+password.
Pre-auth key for non-interactive login (tskey-auth-xxx). Alternative: set ZTNA_AUTH_KEY env var.
Authenticate via GitHub OAuth (opens browser).
Authenticate via Google OAuth.
SSO login for a given org slug (--sso acme).
Use terminal email/password prompts instead of browser.
Callback timeout. Default 5m.
$ ztna login
→ Opening browser at https://login.quickztna.com/auth?cli_state=...
→ Waiting for callback on http://127.0.0.1:51823/callback
✓ Authenticated as you@company.com
✓ Org: acme (acme.zt.net)
✓ Tokens saved to ~/.config/ztna/tokens.json ztna logout
Clear local tokens and authentication state. Org ID is preserved so re-login returns you to the same tenant.
$ ztna logout
✓ Cleared ~/.config/ztna/tokens.json
✓ Preserved org_id (use `ztna login` to resume) Connection lifecycle
ztna up
Alias: ztna connect. Register the machine (if not registered) and bring the WireGuard tunnel online. Auto-detects elevation and falls back to userspace if not root/admin. On Windows the command auto-daemonizes after preflight; on Linux/macOS it stays foreground unless --daemon.
Pre-auth key for non-interactive registration. Reads ZTNA_AUTH_KEY if unset.
Override machine hostname.
Offer this machine as an exit node for other peers.
Route all outbound traffic through a peer (IP or auto).
Keep local LAN reachable when routing through an exit node.
Comma-separated CIDRs to advertise (e.g. 10.0.0.0/24,10.1.0.0/24).
Apply ACL tags (tag:prod,tag:k8s).
Accept subnet routes advertised by peers.
Run as subnet gateway (headless, requires --advertise-routes).
Block all incoming connections.
Enable SSH server on the tailnet IP.
Enable MagicDNS. Default true.
DNS search domain.
Force userspace networking (no TUN device).
Allow non-root user to manage the tunnel.
Run VPN in background; logs to ~/.config/ztna/ztna.log.
Force re-authentication before connecting.
Reset all preferences to default.
$ ztna up --auth-key tskey-auth-abc123 --hostname laptop-alex --advertise-tags tag:laptop,tag:eng
→ Detecting elevation... root ✓
→ Loading WireGuard kernel module... ✓
→ Generating ML-KEM-768 + X25519 hybrid keypair
→ Registering machine "laptop-alex"
✓ Machine ID: m_Pq7r2w · Tailnet IP: 100.64.1.42
✓ MagicDNS: laptop-alex.acme.zt.net
→ Exchanging keys with 99 peers (48 direct / 51 via DERP)
→ All peers configured with hybrid PSK
✓ Tunnel up — connected as you@company.com, quantum-safe ztna up a second time will refuse to start — see ~/.config/ztna/ztna.pid. TUN and userspace modes use separate PID files.
ztna down
Alias: ztna disconnect. Stop the tunnel, kill the daemon, and send an offline heartbeat so the control plane marks this machine offline immediately.
Confirm risky action (e.g. --accept-risk lose-ssh when you're SSH-connected through the tunnel you're tearing down).
$ ztna down
→ Sending offline heartbeat
→ Stopping daemon (PID 24891)
→ Removing WireGuard interface
✓ Disconnected. Use `ztna up` to reconnect. Status & inventory
ztna status
One-shot status: machine info, auth state, tailnet IP, DERP region, peer summary, ACL-blocked peers, exit node config.
Output as JSON (machine-parseable).
Only include online peers in output.
Include peer list. Default true.
$ ztna status
QuickZTNA v3.2.0 · linux-amd64
────────────────────────────────
● Connected — quantum-safe (PQC active)
Tailnet: acme.zt.net (org_9fX2kR)
Machine: laptop-alex · 100.64.1.42
Mode: TUN (kernel) · MTU 1280
DERP: derp-blr (38ms)
Peers: 99 online · 0 offline · 0 ACL-blocked
Exit: (none)
Recent peers (top 5):
db-primary 100.64.1.12 4.2ms direct · tag:prod
ci-runner-03 100.64.1.18 38ms direct · tag:ci
eu-edge-07 100.64.1.31 172ms derp-lon
... ztna peers
List all peers (or one specific peer) with NAT traversal and connection details.
Optional specific peer to detail.
Machine-parseable output.
Only reachable peers.
$ ztna peers --active
NAME TAILNET IP PATH LATENCY TAGS LAST SEEN
laptop-alex 100.64.1.42 self — tag:laptop now
db-primary 100.64.1.12 direct 4.2ms tag:prod 5s ago
ci-runner-03 100.64.1.18 direct 38ms tag:ci 20s ago
eu-edge-07 100.64.1.31 derp 172ms tag:edge 45s ago
legacy-vpc 100.64.1.55 direct 12ms tag:legacy 1m ago
... ztna ip
Show this machine's tailnet IP, or look up a peer's IP by name.
$ ztna ip
100.64.1.42 ztna whois
Reverse lookup: find which machine owns a tailnet IP.
$ ztna whois 100.64.1.18
Machine: ci-runner-03
ID: m_Kx9j4m
IP: 100.64.1.18
Owner: bot@acme.com
Endpoint: 34.72.91.44:41824
DERP: blr · online
Tags: tag:ci, tag:ephemeral ztna machines list
List every machine in your org. Admin-only commands (approve, quarantine, delete) are in the dashboard or via REST API.
$ ztna machines list
NAME IP OS STATUS TAGS LAST SEEN
laptop-alex 100.64.1.42 linux online tag:laptop now
db-primary 100.64.1.12 linux online tag:prod,tag:db 5s ago
ci-runner-03 100.64.1.18 linux online tag:ci 20s ago
legacy-vpc 100.64.1.55 linux offline tag:legacy 4h ago
...
Total: 100 machines (99 online, 1 offline) ztna auth-keys list
List auth keys in your org. Admin-only — backend returns 403 for non-admins.
$ ztna auth-keys list
PREFIX REUSABLE EPHEMERAL USES TAGS EXPIRES
tskey-auth-abc12… yes no 87 tag:laptop in 45 min
tskey-auth-xyz78… no yes 1 tag:ci used
tskey-auth-qwe45… yes no 0 tag:prod revoked Networking commands
ztna ping
Ping a peer through the mesh. Tries three paths: IPC (accurate tunnel latency), control-plane ping (userspace fallback), raw ICMP (TUN fallback).
Peer name, MagicDNS name, or tailnet IP.
Number of probes. Default 5.
Per-probe timeout. Default 3s.
$ ztna ping db-primary
PING db-primary.acme.zt.net (100.64.1.12) via WireGuard (PQC)
pqc_pong bytes=64 ttl=64 time=4.2ms path=direct
pqc_pong bytes=64 ttl=64 time=4.5ms path=direct
pqc_pong bytes=64 ttl=64 time=4.1ms path=direct
pqc_pong bytes=64 ttl=64 time=4.8ms path=direct
pqc_pong bytes=64 ttl=64 time=4.3ms path=direct
5 probes sent, 5 received, 0% loss
min/avg/max = 4.1ms / 4.38ms / 4.8ms ztna nc
Netcat-like TCP tunnel through the mesh. Pipes stdin/stdout to/from a peer's port.
$ ztna nc db-primary 5432
# Connected — raw TCP stream. Type bytes, see bytes. Ctrl+D to close. ztna ssh
SSH to a peer through the tailnet. Resolves the peer name to its tailnet IP and invokes your system ssh with StrictHostKeyChecking=accept-new.
$ ztna ssh db-primary
# Resolved db-primary → 100.64.1.12, connecting as alex@100.64.1.12
Welcome to db-primary (Ubuntu 24.04 LTS) DNS commands
ztna dns query
Resolve a hostname via MagicDNS. Tries the hostname as-is, then appends the search domain if not found.
$ ztna dns query db-primary
100.64.1.12 (db-primary.acme.zt.net)
$ ztna dns query db-primary.acme.zt.net
100.64.1.12 ztna dns status
Show DNS resolver configuration.
$ ztna dns status
MagicDNS: enabled
Search domain: acme.zt.net
Resolver: 100.100.100.100
Upstream: 1.1.1.1, 1.0.0.1 Configuration
ztna set
Modify settings dynamically without restarting the tunnel. All flags are optional; each one changes just one setting.
Change machine name.
Use exit node (peer IP, auto, or off).
Keep LAN reachable with exit node.
Offer this machine as exit node.
Advertise CIDRs.
Block incoming connections.
Enable SSH server on tailnet IP.
Accept peer routes.
Enable auto-updates.
Set machine tags.
$ ztna set --hostname laptop-alex-new
✓ Hostname updated to "laptop-alex-new"
✓ Control plane notified ztna route list
List advertised and approved subnet routes across the tailnet.
Filter to one machine.
Machine-parseable output.
$ ztna route list
MACHINE IP ADVERTISED APPROVED EXIT NODE
laptop-alex 100.64.1.42 — — no
db-primary 100.64.1.12 10.0.0.0/16 10.0.0.0/16 no
eu-edge-07 100.64.1.31 0.0.0.0/0, ::/0 0.0.0.0/0, ::/0 yes
legacy-vpc 100.64.1.55 192.168.5.0/24 (pending) no ztna route approve / reject / approve-exit-node / reject-exit-node
Approve or reject advertised routes for a machine. Hidden from -h output (admin-only — backend enforces role).
$ ztna route approve legacy-vpc
✓ Approved 1 route for legacy-vpc:
- 192.168.5.0/24 ztna exit-node list / suggest
Discover and rank exit nodes available in your tailnet.
$ ztna exit-node list
NAME IP DERP ONLINE APPROVED SELECTED
eu-edge-07 100.64.1.31 lon yes yes *
us-edge-03 100.64.1.88 nyc yes yes
apac-edge-01 100.64.1.120 blr yes yes ztna split-tunnel list
Show CIDRs that bypass the VPN (split-tunneling).
$ ztna split-tunnel list
BYPASS CIDR REASON
10.100.0.0/16 LAN (RFC1918)
169.254.0.0/16 Link-local
224.0.0.0/4 Multicast Certificates & secrets
ztna cert
Issue a TLS certificate for a tailnet hostname. Signed by the per-org internal CA. No wildcards.
Defaults to this machine's registered name.
Output path for cert (default DOMAIN.crt).
Output path for key (default DOMAIN.key).
Start demo HTTPS server on :443 using the issued cert.
$ ztna cert
→ Requesting cert for laptop-alex.acme.zt.net
✓ Certificate: laptop-alex.acme.zt.net.crt (signed by acme-root-ca)
✓ Private key: laptop-alex.acme.zt.net.key
✓ Valid until: 2026-07-19 · 90 days ztna secrets list / get / set / delete / rotate
Encrypted secrets vault (Business+ plan). AES-256-GCM, per-org DEK, role-scoped access.
Secret value (reads from stdin if omitted).
Secret description.
generic, database, api_key, ssh_key, certificate, or token. Default generic.
$ ztna secrets list
NAME CATEGORY VERSION ROLES ROTATED EXPIRES
db-password database 3 admin,dba 2 days ago in 88 days
github-token api_key 1 admin,ci 3 hours ago in 30 days
ssh-deploy-key ssh_key 2 admin,deploy 1 month ago in 60 days Access control
ztna acl list
Show current ACL rules for your org.
$ ztna acl list
[
{
"id": "acl_7K9xq",
"src": "tag:laptop",
"dst": "tag:prod",
"proto": "tcp",
"ports": "22,443,5432",
"time_hour": "09-18",
"day_of_week": "mon-fri",
"source_country": "IN"
},
{
"id": "acl_8Ly4r",
"src": "tag:admin",
"dst": "*",
"proto": "*",
"ports": "*"
}
] ztna acl test
Simulate an ACL check. Can source reach destination on this port?
Source machine name or ID.
Destination machine name or ID.
Destination port.
tcp, udp, icmp, or *. Default *.
$ ztna acl test --src laptop-alex --dst db-primary --port 5432 --proto tcp
✓ ALLOW
Matched rule: acl_7K9xq
src: tag:laptop
dst: tag:prod
port 5432: in 22,443,5432 ✓
time: 14:23 UTC (Thursday) ✓ (09-18 mon-fri)
source_country IN ✓ Posture & security
ztna posture status
Show device posture compliance — OS version, disk encryption, firewall, AV — and server verdict.
$ ztna posture status
Device Posture · laptop-alex
─────────────────────────────
OS Version: Ubuntu 24.04.1 LTS ✓ (required: 22.04+)
Disk Encryption: LUKS2 (active) ✓
Firewall: ufw (enabled) ✓
Antivirus: clamav (active) ✓
Screen Lock: enabled (5min) ✓
Auto-Update: enabled ✓
Server Verdict: COMPLIANT — all posture checks pass
Policy: posture-strict-prod (org default) ztna threat check
Query threat intelligence feeds for IP, domain, or file hash. Auto-detects target type.
$ ztna threat check 185.220.101.42
Target: 185.220.101.42 (IPv4)
Confidence: 98% malicious
Sources:
AbuseIPDB: reported 47 times (last 7d)
VirusTotal: flagged by 12/88 engines
QuickZTNA IOC: match — category: tor-exit-node
Recommendation: BLOCK ztna audit list
List recent audit log entries. Scoped to your org.
Days to look back. Default 7.
json or csv. Default json.
$ ztna audit list --days 1 --format csv | head -5
timestamp,org_id,user_id,action,resource_type,resource_id,ip_address
2026-04-19T17:32:04Z,org_9fX2kR,user_5k4aLw,auth.login,user,user_5k4aLw,104.28.42.88
2026-04-19T17:34:12Z,org_9fX2kR,user_5k4aLw,auth_key.created,auth_key,ak_Pq7r,104.28.42.88
2026-04-19T17:45:01Z,org_9fX2kR,bot_CI,machine.registered,machine,m_K9xq,34.72.91.44
2026-04-19T18:02:18Z,org_9fX2kR,user_5k4aLw,acl_rule.updated,acl_rule,acl_7K9xq,104.28.42.88 ztna compliance report
Aggregate compliance snapshot: machines, posture rate, ACL counts, certs, threats. Useful for monthly review.
$ ztna compliance report --json
{
"generated_at": "2026-04-19T18:00:00Z",
"org_id": "org_9fX2kR",
"machines": { "total": 100, "online": 97, "quarantined": 1 },
"posture": { "compliant": 96, "non_compliant": 4, "rate": 96 },
"acl": { "rules": 18, "deny_default": true },
"certs": { "active": 42, "expiring_30d": 3 },
"threats": { "detected_7d": 12, "blocked_7d": 12 }
} Diagnostics
ztna netcheck
Network reachability probe. Tests UDP, STUN, DERP, and NAT type. Run this first when something isn't connecting.
$ ztna netcheck
QuickZTNA netcheck — 2026-04-19T18:03:12Z
────────────────────────────────────────
UDP: OK
IPv4: 104.28.42.88
IPv6: 2606:4700::1
Public UDP: 104.28.42.88:51823
NAT type: endpoint-independent (full cone)
STUN (blr): OK · 38ms
STUN (nyc): OK · 204ms
STUN (lon): OK · 172ms
STUN (sfo): OK · 242ms
Nearest DERP: blr (38ms) — preferred
DERP blr TLS: OK · TLS 1.3 · X25519Kyber768
Verdict: OK — direct P2P should succeed for most peers ztna debug goroutines / derp / metrics
Developer diagnostics. goroutines dumps all stacks; derp shows relay state; metrics dumps daemon counters.
$ ztna debug goroutines | head -20
goroutine 1 [running]:
runtime/pprof.writeGoroutineStacks(...)
...
goroutine 42 [select, 3 minutes]:
ztna/client/pkg/agent.(*Agent).heartbeatLoop(...) ztna log
Tail the daemon log. Follows like tail -f when -f.
Follow output.
Number of recent lines. Default 50.
Truncate the log file.
$ ztna log -n 5
2026-04-19T17:45:02.113Z INFO agent: peer db-primary path changed direct → derp-lon
2026-04-19T17:45:02.201Z INFO dns: refreshed MagicDNS zone (100 entries)
2026-04-19T17:45:10.004Z INFO agent: peer db-primary path changed derp-lon → direct
2026-04-19T17:45:12.887Z INFO heartbeat: 99/99 peers reachable
2026-04-19T17:45:15.012Z DEBUG pqc: rotated PSK for peer ci-runner-03 (age 24h) ztna bugreport
Generate a diagnostic zip for support. Sanitized: private keys and tokens are stripped; logs are capped to 200 KB; config is redacted.
$ ztna bugreport --output /tmp/issue.zip
Collecting bugreport...
✓ Sanitized config
✓ Redacted state
✓ Last 200 KB of daemon log
✓ Status snapshot
✓ System info (OS, kernel, arch)
Bundle: /tmp/issue.zip (38 KB)
Email to: support@quickztna.com ztna metrics print / write
Expose Prometheus-format metrics. print dumps to stdout; write atomically writes to a file (for node_exporter --collector.textfile.directory).
$ ztna metrics print | head -8
# HELP ztna_peer_count Peers currently reachable
# TYPE ztna_peer_count gauge
ztna_peer_count{path="direct"} 48
ztna_peer_count{path="derp"} 2
# HELP ztna_tx_bytes_total Bytes transmitted through the tunnel
# TYPE ztna_tx_bytes_total counter
ztna_tx_bytes_total 248329142
... Version & updates
ztna version
$ ztna version
QuickZTNA v3.2.0
Build: 2026-04-12T09:18:44Z
Commit: a4b8e2c
OS/arch: linux/amd64
Go: 1.24.2 ztna update
Check for and apply client updates. Atomic apply: downloads to temp, verifies checksum, swaps binary, restarts service.
Check only, don't download.
Machine-parseable output.
Skip confirmation.
$ ztna update --check
Current: v3.2.0
Latest: v3.2.1 (released 2 days ago)
Update? Run `ztna update` to apply. Profiles & multi-org
ztna switch
Switch between orgs or saved profiles without logging out. Interactive picker if no argument is given.
Org slug or profile name. Omit for interactive picker.
List available orgs or profiles.
Treat name as a profile rather than an org slug.
$ ztna switch --list
ORGS (* = current)
* acme org_9fX2kR acme.zt.net
personal org_3mN7p personal.zt.net
consulting org_8Jk2w consulting.zt.net ztna profile list / create / delete
Profiles are saved connection configs — useful if you connect to multiple self-hosted QuickZTNA instances.
$ ztna profile list
NAME ORG_ID API URL ACTIVE
prod org_9fX2kR https://login.quickztna.com *
staging org_5tH1r https://staging.login.quickztna…
self-host — https://zt.internal.acme.corp System integration
ztna install / uninstall
Install or remove ztna as a system service: systemd on Linux, launchd on macOS, Windows service on Windows.
Overwrite existing service files (install only).
$ sudo ztna install
→ Writing /etc/systemd/system/quickztna-svc.service
→ systemctl daemon-reload
→ systemctl enable --now quickztna-svc
✓ quickztna-svc is active (running) ztna install and ztna uninstall that still work — documented separately in the Go source for backward compatibility with older install scripts. Prefer the shorter forms.
Shell & misc
ztna completion
Generate shell completion scripts for bash, zsh, fish, or PowerShell.
$ ztna completion bash > /etc/bash_completion.d/ztna ztna wg-config export
Export raw WireGuard config — for debugging, or if you want to configure WireGuard manually on a gateway device.
Machine ID. Defaults to this machine.
Return JSON with metadata instead of raw wg0.conf.
$ ztna wg-config export
[Interface]
PrivateKey = SF8z...
Address = 100.64.1.42/10
DNS = 100.100.100.100
MTU = 1280
[Peer]
PublicKey = K9xqP...
PresharedKey = 2bN4r... # derived from ML-KEM-768 + X25519 hybrid
Endpoint = 52.34.12.88:41824
AllowedIPs = 100.64.1.12/32
PersistentKeepalive = 25
... ztna licenses
Print open source license information.
$ ztna licenses
QuickZTNA uses the following open-source components.
Full text: https://quickztna.com/licenses Exit codes
All commands follow the same convention:
0— success1— error (Cobra's default; prints error message to stderr)130— interrupted (Ctrl+C)
Summary
39 primary commands across 14 categories. All are implemented in the current release.
4 commands are hidden from -h (route approve/reject, route approve-exit-node/reject-exit-node)
— still callable, but admin-only on the backend.
1 command is a thin stub (licenses — redirects to the website).
Everything else is full-featured with proper error handling, JSON output, and multi-platform support (Linux, macOS, Windows; amd64 and arm64).