CLI Reference
CLI Commands
Full reference of commands and flags.
The ngris binary has two command families: tunneling commands (ngris http, ngris tcp, ngris webfm…) that run a live agent from your machine, and application-hosting commands (ngris apps list, ngris deploy, ngris apps promote…) that build and ship apps to the Ngris edge. They're the same binary; see Application hosting commands below for the deploy family.
Global
Top-level commands and help.
Endpoints
Forward HTTP, TCP or UDP traffic, or expose a directory as a web file manager. On a TCP tunnel, add --proxy-protocol v1|v2 to hand the backend the real client IP.
Traffic policy
Attach edge rules (IP allow/deny, rate limit, header rewrites, force-HTTPS, compression) at connect time — from shorthand flags or a --traffic-policy file. Requires the traffic-policy entitlement. See Traffic Policy Rules → Configure from the agent CLI for the file format.
Endpoint auth
Put an endpoint behind a login at connect time: HTTP Basic auth (--basic-auth user:pass, repeatable, passwords ≥ 8 chars; turns on username/password sign-in only) and/or mutual TLS (--mutual-tls disabled|optional|required with --client-ca <file>; mTLS needs the mtls_attachment entitlement; client certificates are checked on HTTP(S) endpoints only, so use them with ngris http). Restarting without a flag removes what it had set (a run that logs in with a key doesn't: see Key authentication below); settings made in the dashboard stay. See Security & Access → Endpoint authentication from the agent CLI.
Key authentication (Preview)
Log a tunnel in with a key instead of a token (see Key authentication). ngris key sign <code> signs the code shown in the dashboard (Security → Agent keys → Upload key); paste its output there. It signs with --private-key-file <file> if given, else the key saved with ngris auth, else ~/.config/ngris/agent_key. ngris auth --private-key-file <file> saves the key for every tunnel; --auth-method token|key and --private-key-file <file> set it for one tunnel command. Environment variables can't set them. A key covers tunnels only: app commands and the endpoint auth and traffic policy flags (and webfm --rw) need a token.
Client certificates
Get a certificate for a device from a certificate authority ngris manages for your account (create the authority in the dashboard: Security → mTLS). ngris client-cert request makes the key on the device and writes <device>.crt, <device>.key and ca.pem. Files are never overwritten, so renew into a new folder with --out-dir. list shows your certificates and revoke makes your endpoints refuse one from the device’s next request. See Security & Access → Let ngris issue the certificates.
HTTPS backend
Give the http command an https:// local address and the agent forwards over TLS, verifying the backend certificate — --upstream-ca <file> for a private CA, --upstream-client-cert/--upstream-client-key for mTLS, --upstream-server-name for the SNI, and --upstream-host to rewrite the Host header the backend sees. The local port is optional — it defaults from the scheme (https://→:443, http://→:80). See Connect to an HTTPS backend below.
Inspector
Tail, search, replay, and export the requests captured by a running tunnel, over its local loopback API. replay re-sends a captured request to the tunnel's own local backend.
Config & Diagnostics
Run bare ngris for a quick HTTP tunnel to localhost:80 — it accepts the same http flags and loads your personal config.yaml (or the file you pass with --config; older versions also read config.yaml from the working directory); override settings with the agent’s environment variables (for example API_TOKEN, AGENT_REGION). ngris config check validates the resolved config (file + env) without connecting — a non-zero exit means it's invalid.
ngris --version
ngris --help
ngris auth --token xyz
ngris auth --private-key-file ~/.config/ngris/agent_key # key authentication (Preview)
ngris key sign <code>
ngris version
ngris http <addr> [flags]
ngris tcp <addr> [flags]
ngris udp <addr> [flags]
ngris webfm <path> [--rw] # Web File Manager (read-only by default)
ngris tcp 5432 --proxy-protocol v2 # preserve the real client IP (PROXY protocol; TCP only)
ngris http 3000 --cidr-allow 10.0.0.0/8 --rate-limit 100/1m
ngris http 3000 --force-https --response-header-add X-Frame-Options:DENY
ngris http 3000 --traffic-policy ./policy.yaml # YAML/JSON rule file
ngris http 3000 --basic-auth alice:s3cretpw # HTTP Basic (repeat for more users)
ngris http 3000 --mutual-tls required --client-ca ./client-ca.pem
ngris webfm ./site --rw --basic-auth admin:s3cretpw # protect a writable file server
ngris key sign <code> # paste the output into the dashboard
ngris key sign --private-key-file ./agent_key <code> # a key in another place
ngris auth --private-key-file ~/.config/ngris/agent_key # use the key for every tunnel
ngris http 3000 --auth-method key --private-key-file ./agent_key
ngris client-cert request --ca "Company laptops" --name laptop-anna # key made on this device
ngris client-cert request --ca "Company laptops" --name laptop-anna --out-dir ./renewed # renew
ngris client-cert list [--ca <name|id>]
ngris client-cert revoke <id> [--reason TEXT] [--yes]
ngris http https://localhost:8443 # verified TLS to the backend
ngris http https://example.com --upstream-host rewrite # public vhost/CDN (:443 default, sends Host: example.com)
ngris http https://localhost:8443 --upstream-ca ./ca.pem # private / self-signed CA
ngris http https://localhost:8443 \
--upstream-client-cert ./client.pem --upstream-client-key ./client.key # mTLS
ngris inspect --status 4xx,5xx [--search <str>] [--follow] [--json]
ngris replay --id <req_id> --set-header "X-Debug: 1"
ngris export --format json > requests.json
# Bare ngris = HTTP tunnel to localhost:80 (accepts the http flags),
# reading your personal config.yaml (or --config <file>)
ngris
ngris --basic-auth admin:s3cretpw --url myapp.ngris.io # bare + http flags
# Validate config (file + env) without connecting — non-zero exit if invalid
ngris config check
ngris config check --config ./config.yaml --json
# Region preference via environment variable (the server address
# itself is not configurable: discovery picks it)
AGENT_REGION=us-va ngris
Application hosting commands
The deploy CLI family builds and ships apps to the Ngris edge — static sites, managed backends, full-stack apps, and serverless functions. Unlike the tunneling commands (above), nothing keeps running on your machine: each command performs an action against your account and returns. Many accept --json for machine-readable output.
Authenticate
ngris auth --token <token> saves an account API token to the config; every app-hosting command then acts on that account with full dashboard parity. Create the token from the dashboard under Account → Security → Auth token. Key authentication (Preview) covers the tunnel login only: these commands always need a token.
Targeting an app
Each app command targets one application by its UUID with --app <uuid>. To skip the flag, run ngris apps link --app <uuid> once in a project directory — later commands in that directory infer the app automatically.
List & create
ngris apps list lists the account's applications (name, type, live version). ngris apps create <name> --type static|managed|fullstack|function creates an app of any type; add --source git --repo <url> (with optional --branch / --git-token) for a git app, and --root-dir <dir> to build from a monorepo subdirectory. The build settings flags (below) also work on create, so the first build uses them. apps create has no --subdomain: an app gets its address from an endpoint, so create one for it afterwards. For a static site with its address in one step, run ngris deploy <dir> --name <app> --subdomain <sub>.
Deploy & redeploy
ngris deploy [DIR] zips DIR (default .) and deploys it. Target an existing app with --app <uuid>, or pass --name <n> (with an optional --subdomain) to create-and-deploy in one step. --preview builds a not-live candidate; --version <label> tags the deploy. ngris apps redeploy --app <uuid> (alias ngris apps restart) rebuilds and redeploys the app's current source; add --no-activate to build a candidate without going live.
Promote & roll back
ngris apps promote <deployUUID> --app <uuid> makes a preview/candidate deploy live with no rebuild — an instant pointer flip. ngris apps rollback <deployUUID> --app <uuid> reverts to an earlier deploy, also without rebuilding. Run ngris apps deploys --app <uuid> to list the deploy history (version, status, whether it's rollbackable, and the UUID) — those <deployUUID>s are what promote, rollback, and apps logs take.
Logs & status
ngris apps logs <deployUUID> --app <uuid> streams that deploy's build log. When a build succeeded but the app crashes at runtime, ngris apps logs --runtime --app <uuid> tails the running managed pod's stdout/stderr (--tail N to bound it, --previous for the crashed instance). ngris apps status --app <uuid> reports live managed-runtime status — instances, replicas, restarts.
Settings & env vars
ngris apps settings --app <uuid> edits serving settings (--name, --publish-dir, --root-dir, --index) and build settings: --install-command, --build-command and --output-dir (a static app, a full-stack front end), --start-command (a managed app, a full-stack backend; a managed app also takes install and build). An empty value (--build-command='') goes back to detection; the next deploy or ngris apps redeploy uses them. ngris env <list|pull|set|rm> --app <uuid> (or ngris apps env …) manages environment variables; the subcommand comes right after env. Every subcommand takes --env production|preview|all (which deploys get the variable):
listprints a table of every variable with its environment, its source (yours, or a connected database) and its value (secret values hidden).--envnarrows it to one environment.pullprints, as a dotenv file, the variables a deploy in that environment gets (defaultproduction; secret values hidden).--include-dbadds the variables your connected databases provide.set NAME=VALUEadds or updates one (default--env all;--secretencrypts it).rm NAMEremoves it from one environment (defaultall). A name that isn't set there gives a “not found” error and nothing changes.
Cron jobs
ngris apps cron --app <uuid> lists the app's cron jobs: state, schedule, method, path, next run (UTC), last status, and UUID. add --schedule "0 * * * *" creates one (--path, default /; --method, default GET; optional --name, repeatable --header "Name: value", and --paused). run <job> fires a job now and exits non-zero unless the app answered 2xx/3xx; pause, resume, and rm take the same <job>: the job's UUID, a unique prefix of it, or its name. Schedules are UTC. Cron jobs are a paid-plan feature.
Link & delete
ngris apps link --app <uuid> links the current directory to an app so later commands can omit --app. ngris apps delete <appUUID> deletes an app and all of its deploys.
/v1/applications* API. CI and automation can call that API directly with an account token — the same operations, same account, same dashboard parity. See the Applications API reference.# Save an account token (dashboard: /account/security/auth-token)
ngris auth --token <token>
ngris apps list # list apps (name, type, live version)
ngris apps list --json # machine-readable
ngris apps create my-site --type static # any type: static|managed|fullstack|function
ngris apps create my-api --type managed \
--source git --repo https://github.com/acme/api --branch main
ngris apps create my-web --type fullstack \
--root-dir apps/web # build from a monorepo subdirectory
ngris deploy # zip . and deploy (needs --app or a link)
ngris deploy ./dist --app <uuid>
ngris deploy --name my-site --subdomain site # create-and-deploy in one step
ngris deploy --preview --version rc1 --app <uuid> # not-live candidate, tagged
ngris apps redeploy --app <uuid> # rebuild current source (alias: ngris apps restart)
ngris apps redeploy --no-activate --app <uuid> # candidate, don't go live
ngris apps deploys --app <uuid> # list deploy history + the UUIDs below
ngris apps promote <deployUUID> --app <uuid> # make a candidate live (no rebuild)
ngris apps rollback <deployUUID> --app <uuid> # revert to a previous deploy
ngris apps logs <deployUUID> --app <uuid> # a deploy's BUILD log
ngris apps logs --runtime --app <uuid> # running managed app's stdout/stderr
ngris apps logs --runtime --tail 200 --previous --app <uuid> # crashed instance
ngris apps status --app <uuid> # instances, replicas, restarts
ngris apps settings --app <uuid> --publish-dir dist --index index.html
ngris apps settings --app <uuid> --root-dir packages/backend
ngris apps settings --app <uuid> --build-command 'npm run build -- --outDir site' --output-dir site
ngris apps settings --app <uuid> --start-command 'node dist/server.js'
ngris env list --app <uuid> # table: NAME ENVIRONMENT SOURCE VALUE
ngris env pull --app <uuid> > .env # production, dotenv (secret values hidden)
ngris env pull --app <uuid> --env preview --include-db # what a branch preview gets
ngris env set API_BASE_URL=https://api.example.com --app <uuid>
ngris env set API_KEY=… --secret --env production --app <uuid>
ngris env rm OLD_FLAG --app <uuid> # from "all"
ngris apps cron --app <uuid> # list jobs
ngris apps cron add --schedule "0 * * * *" --path /api/cron --app <uuid>
ngris apps cron add --schedule @daily --method POST --path /tasks/digest \
--header "X-Job: digest" --name digest --app <uuid>
ngris apps cron run digest --app <uuid> # fire now; exit 1 unless 2xx/3xx
ngris apps cron pause 3f2a9c10 --app <uuid> # UUID, unique prefix, or name
ngris apps cron resume 3f2a9c10 --app <uuid>
ngris apps cron rm 3f2a9c10 --app <uuid>
ngris apps env set CRON_SECRET=… --secret --app <uuid> # sent as Authorization: Bearer
ngris apps link --app <uuid> # later commands in this dir can omit --app
ngris apps delete <appUUID> # delete the app and all its deploys
Connect to an HTTPS backend
By default the agent forwards to your local service over plain TCP. When that service already speaks TLS — an HTTPS dev server, an internal API, or a backend that demands a client certificate — give the http command an https:// address and the agent connects to it over TLS, verifying the backend certificate. Only the connection between the agent and your backend changes; the public endpoint and its certificate are untouched. Every flag below also works in the config file and via the matching AGENT_LOCAL_SERVICE_* environment variable.
The scheme is the switch
The local address decides the transport. ngris http https://localhost:8443 dials the backend over TLS; an http:// address or a bare host:port stays plaintext and byte-for-byte unchanged. Over TLS the backend certificate is verified against your system trust pool by default, using TLS 1.2 or newer.
Verify a private or self-signed certificate
Point --upstream-ca ./ca.pem at the PEM-encoded CA that signed the backend certificate and the agent trusts that CA instead of the system pool — this is what a self-signed dev backend needs. If nothing verifies the certificate the TLS handshake fails and the request is refused; that is verification working, not a misconfiguration.
local_service_insecure_tls: true in the config file or AGENT_LOCAL_SERVICE_INSECURE_TLS=true in the environment. There is deliberately no command-line flag for it — it turns off all certificate checking for the backend connection and must never be used outside local testing.Present a client certificate (mTLS)
When the backend requires mutual TLS, hand the agent a client certificate with --upstream-client-cert ./client.pem and its key with --upstream-client-key ./client.key. The two are a pair: setting one without the other is a startup error, so the agent stops with a clear message rather than connecting without the certificate it was told to present.
Override the SNI / certificate hostname
--upstream-server-name backend.internal sets both the SNI sent during the handshake and the name the backend certificate is checked against. It defaults to the host portion of the local address, so reach for it only when the certificate names something other than what you dial — for instance when you connect to an IP address but the certificate was issued for an internal hostname.
Rewrite the Host header sent to the backend
By default the agent forwards the incoming request's Host header to the backend unchanged — that is your public Ngris endpoint hostname. A backend that routes on Host, such as a shared virtual host or a public CDN, needs its own name instead. --upstream-host rewrite sends the backend's own host (the host portion of the local address), and --upstream-host backend.example.com sends any literal value you name. Left unset it stays passthrough, so the default forwarding path is byte-for-byte unchanged.
Backends on another host
The backend does not have to be on localhost: the agent reaches any host it can route to, so ngris http https://internal-api.corp:8443 forwards your public endpoint straight to an internal service. Two conveniences make remote and vhost backends easy:
- The port is optional. A schemed address defaults its port from the scheme —
https://dials:443andhttp://dials:80— songris http https://internal-api.corpconnects to:443with nothing more to type. Add an explicit:port(for example:8443) whenever the backend listens elsewhere; an explicit port is always honored. - The forwarded
Hostis yours to set. By default the agent passes the incoming request'sHostheader through unchanged — your public Ngris endpoint hostname — while the TLS SNI follows the upstream host. A plain backend ignoresHost; one that routes on it (a shared virtual host or a public CDN) needs the name it expects. Reach for--upstream-host(above):ngris http https://example.com --upstream-host rewriteforwards to a public vhost and the backend receivesHost: example.com.
ngris to change them. The plaintext http:// and bare host:port paths are unaffected.# Verified against the system trust pool (TLS 1.2+)
ngris http https://localhost:8443
# Verify a private / self-signed backend cert
ngris http https://localhost:8443 --upstream-ca ./ca.pem
# Mutual TLS — present a client cert (both flags required together)
ngris http https://localhost:8443 \
--upstream-client-cert ./client.pem \
--upstream-client-key ./client.key
# Override the SNI / cert hostname (default: host of the address)
ngris http https://10.0.0.5:8443 --upstream-server-name backend.internal
# Rewrite the Host header for a vhost / CDN backend (port defaults to :443)
ngris http https://example.com --upstream-host rewrite
# A backend on another host (port defaults from the scheme; :8443 here is explicit)
ngris http https://internal-api.corp:8443 --upstream-ca ./corp-ca.pem
# Dev only: skip verification (config / env — there is no CLI flag)
AGENT_LOCAL_SERVICE_INSECURE_TLS=true ngris http https://localhost:8443
Files on disk
What the agent reads and writes on your machine.
| File | What it is |
|---|---|
config.yaml |
The agent reads config.yaml from your user config directory (older versions read one in the working directory first): ~/.config/ngris/config.yaml on Linux, ~/Library/Application Support/ngris/config.yaml on macOS, %AppData%\ngris\config.yaml on Windows. --config <file> names one explicitly. ngris auth --token saves the token (mode 0600) to the --config file, else to ./config.yaml if it exists, else to the user config directory. |
.ngris/project.json |
The app a project directory is linked to, written by ngris apps link (and when ngris deploy --name creates an app). |
| Log file | Only when logging.file is set. |
| Response spool | Temporary files for responses waiting for a slow client (Large transfers), in the ngris/spool directory of your user cache directory: ~/.cache/ngris/spool on Linux, ~/Library/Caches/ngris/spool on macOS, %LocalAppData%\ngris\spool on Windows. See below. |
Response spool
When a public client reads a response slower than your service writes it, the agent keeps reading your service and holds the difference in a spool, then sends it as the client catches up. Each response uses up to 1 MiB of memory, then a temporary file of up to 1 GiB. All responses of one agent together use at most 64 MiB of memory and 2 GiB of disk, and one client IP (for IPv6, its /64) at most an eighth of each. When the spool is full, the agent stops reading your service for that response until its client catches up.
- Location.
ngris/spoolin your user cache directory (on Linux,$XDG_CACHE_HOME/ngris/spoolwhenXDG_CACHE_HOMEis set); without a user cache directory,ngris-agent-spoolin the temp directory. The directory must be the agent's own — a real directory, not a symlink (on Linux and macOS: mode 0700, owned by the agent's user); otherwise the agent uses a new privatengris-spool-*directory in the temp directory, and if it can't create one, spools stay in memory. The agent logs the directory it picked when it starts (a[spool]line). - Files. On Linux and macOS a spool file is deleted right after it is created, so the directory looks empty even while the spool uses disk space; the space is freed when the response ends or the agent exits. On Windows the
spool-*files are deleted when they are closed or the agent exits. At start the agent removes any spool files left in the directory. - tmpfs. If the directory is on tmpfs or ramfs (Linux), spool files are memory: they count against the 64 MiB memory budget, not the 2 GiB disk one.
# Config (when there is no ./config.yaml)
~/.config/ngris/config.yaml # Linux
~/Library/Application\ Support/ngris/config.yaml # macOS
%AppData%\ngris\config.yaml # Windows
# Response spool (up to 64 MiB memory + 2 GiB disk per agent)
~/.cache/ngris/spool # Linux
~/Library/Caches/ngris/spool # macOS
%LocalAppData%\ngris\spool # Windows
Troubleshooting
“This ngris agent … is too old”
The edge refused an agent that doesn't support flow control (every agent before the current release), and the agent stopped. A message saying the agent “is older than the minimum supported version” means the same: update the agent and start it again. See Updating the agent.
“This ngris CLI … is older than the minimum supported version” (426)
A CLI command such as ngris deploy or ngris apps list was refused because the agent is older than the minimum version Ngris supports. The command prints the reason and stops, for example error: upgrade required (426): This ngris CLI (v<your version>) is older than the minimum supported version <minimum>. Update: https://ngris.com/install; older releases print it as HTTP 426: …. Update the agent and run the command again. See Updating the agent.
Uploads fail with 408, 400 or 502
408: the client sent its body too slowly (under 64 KiB per minute) or stopped for 60 seconds. 400: the body was shorter than its Content-Length. 502: your service stopped reading the body for 2 minutes. See Large transfers & timeouts.
A long download stops
The edge ends a response when no bytes reach the client for 60 seconds, or when the client takes under 64 KiB in 60 seconds while data waits for it. See Large transfers & timeouts.
Port already in use
Change the local port or stop the conflicting process.
Firewall/Corporate proxy
Allow outbound QUIC/HTTPS to Ngris regions or switch transport.
Docker/K8s: Endpoint fails to connect
Ensure the agent can reach your service by name and port.
# macOS/Linux
lsof -i :3000
kill -9 <pid>
# Windows
netstat -ano | findstr :3000
taskkill /PID <pid> /F
# Use HTTP/2 for local connection (gRPC, etc.)
ngris http 3000 --http2
# Use HTTP/3 QUIC (experimental)
ngris http 3000 --http3
# Test connectivity from within container
docker exec -it tunnel-container ping app
kubectl exec -it ngris-pod -- wget -qO- http://web-service:8080/health
FAQ
Is traffic encrypted?
Yes, end-to-end encryption is enforced. The connection between your agent and our edge servers uses mutual TLS. Public endpoints serve HTTPS with auto-renewed certificates via ACME. Data in transit is always protected.
Can I self-host the Ngris server?
No. Ngris is a hosted SaaS only — there is no on-premise, private-cloud, or self-hosted edition. You run the small agent (or the Kubernetes operator) next to your service; the edge, control plane, and dashboard are operated by Ngris.
What happens when my endpoint disconnects?
If the agent disconnects, the public endpoint returns a 502 Bad Gateway error. Upon reconnection, the connection resumes automatically if using persistent credentials. For critical services, implement health checks and alerts via the API.
How long are request logs retained?
How long traffic logs are kept depends on your plan: GET /v1/account/plan returns it as plan_limits.traffic_log_retention_days, and every GET /v1/traffic/logs response carries your account's window as retention_days. On a paid plan you can keep them longer with the extended log retention add-on, billed per extra day.