Endpoints & Tunnels

HTTP / HTTPS

Forward public HTTPS to a local HTTP or HTTPS service. The public edge terminates TLS and serves your traffic over HTTP/1.1, HTTP/2, and HTTP/3 (QUIC) — clients pick whichever they support.

Public hostname: Pass --url <hostname> to attach a reserved subdomain or custom domain. Omit --url and the server provisions an ephemeral endpoint — a randomly generated hostname that lives for the duration of the agent process.
Best Practice: Use the traffic inspector in the dashboard to capture and debug requests in real time.
Local protocol: The agent ↔ local service hop is HTTP/1.1 by default. Set tunnel.local_http_version to http2 (h2c) or http3 (QUIC, experimental — local target must serve HTTPS) for backends that require it.
Forward HTTP(S)
# Reserved hostname
ngris http 8080 --url blog.ngris.com

# No --url → ephemeral endpoint
# (random hostname, valid for this run)
ngris http 8080

# Local HTTPS (self-signed OK — verified by default,
# set local_service_insecure_tls: true to skip)
ngris http https://localhost:8443 --url secure-app.ngris.com

TCP

Expose raw TCP services like databases or SSH (use with care). The server automatically assigns a free TCP port on the regional ingress (e.g. tcp://eu-fin.ngris.com:17032) and forwards bytes verbatim to your local listener. You don't pick the public port — only the hostname.

Public hostname: Pass --url <hostname> to bind to a reserved hostname (the server still picks the port). Omit --url and the server provisions an ephemeral endpoint — a randomly generated tcp://<random>.ngris.com:<assigned-port> that lives for the duration of the run.
Fixed ports: If you need the public port pinned (e.g. firewall allow-list), upgrade to a plan with the dedicated_ports entitlement and reserve one from the dashboard.
Preserve the client IP: Add --proxy-protocol v2 (or v1) and the agent prepends a PROXY protocol header to the backend connection, carrying the original client's IP and port — so your database, SSH, or TLS-passthrough backend logs the real client instead of the agent. Your backend must be configured to expect it (e.g. Postgres behind pgbouncer, or proxy_protocol on in nginx/HAProxy); otherwise it reads the header as junk and drops the connection. HTTP tunnels don't need this — the edge already forwards the client IP as X-Forwarded-For.
Warning: For TCP services, enable access controls (see Security) and avoid exposing production databases. Never expose privileged ports without strong authentication.
Expose raw TCP
# Reserved hostname, server-assigned port
ngris tcp 5432 --url mydb.ngris.com

# SSH on a reserved hostname
ngris tcp 22 --url ssh.ngris.com

# No --url → ephemeral
# (random hostname + server-assigned port, valid for this run)
ngris tcp 5432

# Preserve the real client IP to the backend
# (backend must be configured to expect PROXY protocol)
ngris tcp 5432 --url mydb.ngris.com --proxy-protocol v2

UDP

Tunnel datagram services like DNS, WireGuard, or QUIC-based protocols. Like TCP, the server automatically assigns a free UDP port on the regional ingress (udp://<hostname>:<assigned-port>) — you supply only the hostname. Packets are forwarded to your local listener and the response is shipped back to the originating client.

Public hostname: Pass --url <hostname> to bind to a reserved hostname (the server still picks the port). Omit --url and the server provisions an ephemeral endpoint — a randomly generated udp://<random>.ngris.com:<assigned-port> that lives for the duration of the run.
When to use: Forward DNS (port 53), expose a WireGuard server (51820), tunnel game-server traffic, or experiment with a QUIC backend. Sessions are tracked by client tuple with an idle timeout.
Warning: UDP is unauthenticated by default. Pair with access controls for anything sensitive — especially anything resembling open DNS resolvers, which attract reflection attacks.
Expose UDP services
# Reserved hostname, server-assigned port
ngris udp 53 --url dns.ngris.com

# WireGuard server
ngris udp 51820 --url wg.ngris.com

# No --url → ephemeral
# (random hostname + server-assigned port, valid for this run)
ngris udp 5353

# Test it — replace host:port with the assigned public URL
dig @dns.ngris.com -p 17032 example.com

HTTP/3 & QUIC at the edge

HTTP/3 is enabled automatically for every HTTPS endpoint — no flags, no separate command. The public edge advertises HTTP/3 via the alt-svc response header on the first HTTP/1.1 or HTTP/2 request; modern browsers and clients then upgrade to QUIC on subsequent connections.

Real client IPs survive the HTTP/3 hop via PROXY protocol v2 over UDP, so log lines and the traffic inspector see the original client address regardless of the negotiated HTTP version.

Verify HTTP/3: Check the response with curl -sI --http3 https://your-app.ngris.com (curl built with HTTP/3 support) or inspect alt-svc in DevTools.
Confirm HTTP/3 negotiation
curl -sI --http3 https://your-app.ngris.com

# Without --http3 you can still see the advertisement:
curl -sI https://your-app.ngris.com | grep -i alt-svc
# alt-svc: h3=":443"; ma=86400

Custom Domains

Map your domain to an endpoint with automated certificates.

  1. Add your domain in the dashboard (e.g., dev.example.com).
  2. Create a DNS CNAME from dev.example.comyour-org.cname.ngris.com.
  3. Start the endpoint and attach the domain.
Certificates: We provision & renew TLS automatically via ACME using Let's Encrypt. Your domain must resolve correctly for validation.
Wildcard Domains: Unlimited plans support wildcard SSL certificates (e.g., *.staging.example.com). Contact sales for setup.
Attach a custom domain
ngris http 3000 --url dev.example.com

Config File

Use config.yaml in your working directory for repeatable setups across environments. Every key shown below is optional — defaults are baked in; CLI flags and environment variables override the file.

No server address: The tunnel-server endpoint is not set in config — it is resolved at runtime via the discovery service baked into the binary. Use observability.region to express a region preference (e.g. us-east-va, eu-fin).
Config Keys: Access controls, WAF rules, and rate limiting are configured via the dashboard or API — not in the config file. See Endpoint Auth and API Reference.
Environment Variables: All config keys can be overridden with the NGRIS_AGENT_ prefix and underscores for nesting (e.g. NGRIS_AGENT_AUTHENTICATION_TOKEN, NGRIS_AGENT_OBSERVABILITY_REGION).
Multi-tunnel mode: When tunnels: is non-empty it replaces the single tunnel: block above. The TUI is disabled in this mode.
config.yaml — minimal
# config.yaml — place in working directory
authentication:
  token: "your-api-token"

observability:
  region: "us-east-va"   # region preference for discovery

tunnel:
  type: "http"                    # http | tcp | udp
  local_address: "localhost:3000"
  url: "myapp.ngris.com"          # empty → ephemeral endpoint
  name: "My Service Tunnel"
config.yaml — full reference
authentication:
  token: "your-api-token"

logging:
  level: "info"                 # debug | info | warn | error | fatal
  file: "./logs/agent.log"
  format: "text"                # text | json
  control_plane_verbose: false

# Agent → tunnel-server TLS
tls:
  ca_cert_file: ""              # custom CA (PEM) added to the trust store
  ca_cert_only: false           # pin: trust ONLY ca_cert_file
  insecure_skip_verify: false   # UNSAFE — debugging only

# Allow self-signed certs on a local HTTPS backend
local_service_insecure_tls: false

observability:
  region: "us-east-va"

tunnel:
  type: "http"                  # http | tcp | udp
  local_address: "localhost:80" # port, host:port, or file://<dir>
  url: ""                       # empty → ephemeral endpoint
  name: "My Service Tunnel"
  http_port: 9090               # local stats server port
  verbose: false
  max_requests_log: 1000

  # Timeouts
  tcp_dial_timeout: 10s
  tcp_idle_timeout: 5m          # 0 disables
  tcp_keepalive: 30s
  control_read_timeout: 60s
  local_service_timeout: 60s

  # Local protocol + transfer tuning
  local_http_version: "http1.1" # http1.1 | http2 (h2c) | http3 (QUIC)
  chunk_size: 65536             # 64 KiB
  max_concurrent_requests: 100

  # Static file serving (when local_address is file://<dir>)
  disable_directory_listing: false
  index_filename: "index.html"
  file_server_write: false      # WebFM read-write — no built-in auth
  use_webfm_ui: false

  # Load balancing across multiple agents on the same tunnel
  load_balancing:
    weight: 1
    max_fails: 0                # 0 disables
    fail_timeout: 30s

  # Routing identity (server-side routing rules)
  agent_id: ""
  agent_name: ""
  agent_tags: ""                # e.g. "api,v2,production"

# Multi-tunnel: when non-empty REPLACES the single `tunnel:` above
tunnels:
  - type: "http"
    local_address: "localhost:3000"
    url: "app.ngris.com"
    name: "web"
  - type: "tcp"
    local_address: "localhost:22"
    url: "ssh.ngris.com"
    name: "ssh"
  - type: "udp"
    local_address: "localhost:53"
    name: "dns"
Launch from config
# Launch all tunnels defined in config.yaml
# The agent auto-loads config.yaml from the working directory
ngris

# Or run a single tunnel with CLI flags
ngris http 3000 --url myapp
Iris