Configuration via file
You can run cruma from a config file instead of passing flags. The CLI accepts YAML or JSON and will validate the structure before starting the tunnel. The agent watches the file and applies changes to credentials and targets automatically.
Quick reference
| Command | Description |
|---|---|
cruma config locate | Print the default config file path |
cruma config init [PATH] | Create a new config with ANON credentials and no targets |
cruma config reset | Delete and recreate the default config |
cruma config profiles | List known profile IDs |
cruma config show | Display the current configuration |
cruma config set-credentials <TID> <SK> | Set tunnel credentials |
cruma config clear | Remove all frontends, backends, and processes |
cruma config add <type> ... | Add a backend + frontend route |
cruma config remove <INDEX> | Remove a frontend/backend by index |
cruma config remove-process <INDEX> | Remove a hosted process by index |
cruma config update <INDEX> ... | Update a target by index |
cruma config add-listener <PORT> | Add a local listener |
cruma config remove-listener <INDEX> | Remove a local listener by index |
cruma config update-listener <INDEX> ... | Update a local listener by index |
cruma schema | Print the full JSON schema for the config file |
All config subcommands accept -c <PATH> / --config <PATH> to target a specific file instead of the default.
Config file structure
The config file has two required top-level arrays — backends and frontends — plus optional sections for credentials, processes, listeners, and agent behavior.
Older configs that used a flat targets array are no longer supported. The agent will reject them with an error and suggest running cruma config reset to migrate.
Minimal example
tunnel_id: "ANON"
tunnel_secret: "ANON"
backends:
- id: backend-1
kind: http
destination: "127.0.0.1:3000"
frontends:
- hostname: "react-dev"
backend_id: backend-1
Full example
tunnel_id: "demo-tunnel"
tunnel_secret: "beta-secret-123"
tower_server: "tower.cruma.io:443" # optional, default shown
profile: "my-profile" # optional, scopes cached identity
# temp: true # optional, fresh FQDN each run
backends:
- id: web
kind: http
destination: "127.0.0.1:3000"
- id: api
kind: https
destination: "example.com:443"
- id: docs
kind: local-directory
destination: "./public"
allow_directory_indexing: true
render_markdown: true
spa_fallback: false
frontends:
- hostname: "react-dev"
backend_id: web
- hostname: "api"
backend_id: api
- hostname: "api.dev.yourdomain.com"
backend_id: api
- hostname: "docs"
backend_id: docs
processes:
- id: my-app
command: node
args: ["server.js"]
working_directory: "./app"
env:
PORT: "3000"
NODE_ENV: "development"
restart_policy: on-failure # never | on-failure | always
auto_start: true
listeners:
- port: 8443
kind: https
addr: localhost
cert_mode: self_signed
- port: 8080
kind: http
addr: localhost
Backends
A backend describes where traffic goes. Each backend has a stable id that frontends reference.
| Field | Required | Description |
|---|---|---|
id | yes | Stable identifier (e.g. backend-1, web, api) |
kind | yes | http, https, tcp, raw, or local-directory |
destination | yes | host:port for network backends, or a directory path for local-directory |
allow_directory_indexing | no | Show directory listing when no index file is present (local-directory only) |
render_markdown | no | Render .md files as HTML (local-directory only) |
spa_fallback | no | Serve the nearest index.html for 404 paths — standard SPA behavior (local-directory only) |
form_auth | no | Default form-based auth for frontends that don't override it |
api_key_auth | no | Default API key auth ["Header", "value"] |
Backend kinds
http— connect to the backend over plain HTTP.https— connect to the backend over HTTPS (TLS to origin).tcp— forward plain TCP to the backend after TLS has been terminated. For assigned FQDNs (*.tun.cruma.io), TLS is terminated either on the agent or at the Cruma ingress depending on whether the assigned-hostname certificate is active; for CNAME'd custom domains (paid tier only), TLS is terminated on your agent. Either way, the backend receives a plain TCP stream.raw— pass through the TCP stream without additional TLS termination. For CNAME'd custom domains, this means the encrypted TLS stream is forwarded directly to the backend, which must handle TLS itself (true end-to-end TLS between client and backend). For assigned FQDNs (*.tun.cruma.io), TLS is still terminated before the backend, either on the agent or at the Cruma ingress, sorawbehaves the same astcp— the backend receives plain TCP.local-directory— serve static files from a local directory path.
If you need true TLS pass-through to your backend, use raw with a CNAME'd custom domain. With assigned FQDNs, TLS termination normally done using the agent itself as it reuires custom protocols to do DNS01 ACME challenges through the control channels of cruma.io.
Frontends
A frontend maps a hostname pattern to either a backend or a hosted process. Each hostname gets its own frontend entry.
| Field | Required | Description |
|---|---|---|
hostname | yes | Hostname pattern (see below) |
backend_id | one of | References a backend id. Mutually exclusive with process_id. |
process_id | one of | References a process id. Mutually exclusive with backend_id. |
middlewares | no | Ordered list of HTTP middlewares (see Middlewares) |
form_auth | no | Route-level form auth override |
api_key_auth | no | Route-level API key auth override |
Hostname patterns
app— expands toapp.<assigned-fqdn>(e.g.app.abc123.tun.cruma.io)app.yourdomain.com— exact match on a custom domain (requires CNAME)*.yourdomain.com— wildcard match for any subdomain*— matches any hostname (not recommended)
Multiple frontends can point to the same backend (e.g. a shortname and a custom domain both routing to the same service).
Processes
The agent can supervise local processes and optionally create frontend routes backed by them. This is useful for running your app server alongside the tunnel in a single command.
| Field | Required | Description |
|---|---|---|
id | yes | Stable process identifier |
command | yes | Binary or command to run |
args | no | List of arguments |
working_directory | no | Working directory for the process |
env | no | Map of environment variables |
auto_start | no | Start the process automatically (default: true) |
start_on_request | no | Lazily start the process on first incoming request (default: false) |
restart_policy | no | never, on-failure, or always (default: on-failure) |
upstream_protocol | no | HTTP protocol to the process: h1 (default), h2, or h2pk |
upstream_tls | no | Use HTTPS to connect to the process (default: false) |
backend_timeout_seconds | no | Timeout for upstream response in seconds (default: 10) |
idle_timeout_seconds | no | Auto-stop the process after being idle for this many seconds. Combined with start_on_request for full spin-up/spin-down lifecycle. 0 or omitted means never auto-stop. |
To route traffic to a process, add a frontend with process_id instead of backend_id:
processes:
- id: my-app
command: node
args: ["server.js"]
working_directory: "./app"
env:
PORT: "3000"
restart_policy: on-failure
frontends:
- hostname: "myapp"
process_id: my-app
Add a process via CLI:
cruma config add process node --arg server.js --working-dir ./app --env "PORT=3000" --hostname myapp
Additional CLI options for config add process:
--id <ID>— set a stable process identifier (auto-generated if omitted)--no-auto-start— disable auto-start--start-on-request— lazily start on first request--restart <POLICY>— restart policy:never,on-failure,always(default:on-failure)
Listeners
Listeners bind to local addresses so you can access your routes directly on localhost (or on a LAN) without going through the Cruma cloud ingress. They share the same routing table as the cloud ingress.
| Field | Required | Description |
|---|---|---|
kind | no | http, https, or cruma. When omitted, defaults to https if TLS is enabled, http otherwise. |
port | yes | Port number to listen on (ignored for kind: cruma) |
addr | no | localhost (default, loopback only) or all (0.0.0.0) |
tls | no | Legacy field. Prefer using kind instead. |
cert_mode | no | self_signed (default), acme_alpn (Let's Encrypt via TLS-ALPN-01; requires port 443 reachable from the internet), or from_frontend (inherit cert mode from each frontend route) |
listeners:
- kind: https
port: 8443
addr: localhost
cert_mode: self_signed
- kind: http
port: 8080
addr: localhost
Add a listener via CLI:
cruma config add-listener 8443
cruma config add-listener 8080 --no-tls
cruma config add-listener 443 --addr all --cert-mode acme-alpn
cruma config add-listener 8443 --kind https --cert-mode from-frontend
Update a listener:
cruma config update-listener 0 --port 9443
cruma config update-listener 0 --addr all
cruma config update-listener 0 --cert-mode acme-alpn
cruma config update-listener 0 --kind http --tls false
Local-only mode
When local_only: true is set in the config, the agent does not connect to the Cruma cloud. Only local listeners are active. This is useful for running the agent purely as a local reverse proxy or development tool.
local_only: true
backends:
- id: web
kind: http
destination: "127.0.0.1:3000"
frontends:
- hostname: "web"
backend_id: web
listeners:
- kind: https
port: 8443
Access controls
Form-based authentication
Form auth provides a built-in login page with signed-cookie sessions. It can be set as a default on the backend or overridden per frontend.
On a backend (applies to all frontends that don't override):
backends:
- id: web
kind: http
destination: "127.0.0.1:3000"
form_auth:
users:
- ["admin", "secret-password"]
- ["viewer", "viewer-pass"]
secret: "replace-with-a-random-secret"
API key authentication
Require a specific header and value on every request:
backends:
- id: web
kind: http
destination: "127.0.0.1:3000"
api_key_auth: ["X-API-Key", "secret123"]
Middlewares
Frontends support an ordered list of HTTP middlewares. These are applied in order for each request.
frontends:
- hostname: "app"
backend_id: web
middlewares:
- type: redirect_http_to_https
- type: allow_cors
origins: { mode: any }
allow_credentials: false
handle_preflight: true
allow_private_network: false
- type: add_req_header
name: "X-Custom-Header"
value: "my-value"
- type: remove_req_header
name: "X-Unwanted"
- type: add_resp_header
name: "X-Frame-Options"
value: "DENY"
- type: remove_resp_header
name: "Server"
- type: rewrite_path
strip_prefix: "/api"
- type: rewrite_host
to: "internal.example.com"
- type: redirect
location: "https://example.com/new-path"
code: 301
Available middleware types
| Type | Description |
|---|---|
redirect_http_to_https | Redirect HTTP requests to HTTPS |
basic_auth | HTTP Basic Auth (RFC 7617) |
form_auth | HTML form-based login with signed-cookie sessions |
authentication | General authentication with pluggable schemes |
allow_cors | CORS headers and preflight handling |
add_req_header | Add a header to the inbound request |
remove_req_header | Remove a header from the inbound request |
add_resp_header | Add a header to the outbound response |
remove_resp_header | Remove a header from the outbound response |
rewrite_path | Rewrite the request path (strip_prefix or replace_regex) |
rewrite_host | Replace the Host header sent to origin |
redirect | Issue an HTTP redirect (short-circuit) |
cookie_ops | Add or remove cookies |
ip_filter | IP allow/deny filtering (not yet implemented in runtime) |
cache_control_override | Override Cache-Control headers (not yet implemented in runtime) |
cache | In-memory response cache for GET requests (not yet implemented in runtime) |
compression | Content compression negotiation (not yet implemented in runtime) |
rate_limit | Rate limiting by IP, header, cookie, or composite key (not yet implemented in runtime) |
Note: X-Forwarded-Proto, X-Forwarded-For, and X-Forwarded-Host headers are automatically added by the proxy runtime — you don't need to configure them.
Use cruma schema to see the full JSON schema with all middleware fields and validation rules.
Managing configs with the CLI
Adding targets
The cruma config add command creates a backend and a frontend route in one step:
# Add an HTTP target
cruma config add http 127.0.0.1:3000 --hostname react-dev
# Add an HTTPS target with multiple hostnames
cruma config add https example.com:443 --hostname api --hostname api.dev.yourdomain.com
# Add a TCP target
cruma config add tcp 127.0.0.1:5432 --hostname db
# Add a raw TCP target
cruma config add raw 127.0.0.1:4943 --hostname raw-service
# Add a local directory with auth
cruma config add dir ./public --hostname docs --enable-index true --render-markdown --user admin:pass123
# Add a hosted process with a frontend route
cruma config add process node --arg server.js --hostname myapp --env "PORT=3000" --restart on-failure
# Add a process that starts lazily on first request
cruma config add process node --arg server.js --hostname myapp --start-on-request
# Add a process without auto-starting it
cruma config add process node --arg server.js --no-auto-start
Inspecting configuration
cruma config show
This prints an indexed view of all backends, frontends, processes, and listeners, which you need for remove and update commands.
Updating targets
# Change destination
cruma config update 0 --dest 127.0.0.1:4000
# Add/remove hostnames
cruma config update 0 --add-hostname new-host --remove-hostname old-host
# Clear all hostnames at once
cruma config update 0 --clear-hostnames
# Add form auth user
cruma config update 0 --add-user admin:password
# Clear all form auth users
cruma config update 0 --clear-users
# Set API key auth
cruma config update 0 --api-key "X-API-Key:secret123"
# Clear API key auth
cruma config update 0 --clear-api-key
# Enable/disable directory indexing (for directory targets)
cruma config update 0 --enable-index true
# Enable/disable markdown rendering (for directory targets)
cruma config update 0 --render-markdown false
Removing targets
# Remove by index (see 'config show' for indices)
cruma config remove 0
# Remove a process
cruma config remove-process 0
# Remove a listener
cruma config remove-listener 0
Setting credentials
cruma config set-credentials MY_TUNNEL_ID MY_SECRET_KEY
Running from a config file
# Use the default config file
cruma start
# Use a specific file
cruma start ./cruma.yaml
# Shorthand: use -c / --config on the top-level command
cruma -c ./cruma.yaml
Default config vs. multiple configs
The default config file is just a convenience. You can run multiple agents by pointing each one at a different config file (with a different profile set in each config):
cruma start ./configs/app-a.yaml
cruma start ./configs/app-b.yaml
Where each config file specifies its own profile:
# configs/app-a.yaml
profile: "app-a"
# ...
Use multiple configs when you want separate tunnel credentials or distinct target sets per agent. If you only need one agent with multiple services, keep a single config file and add multiple targets.
Top-level settings reference
| Field | Required | Default | Description |
|---|---|---|---|
backends | yes | [] | Backend services |
frontends | yes | [] | Frontend routes |
tunnel_id | no | ANON | Tunnel ID for the public FQDN |
tunnel_secret | no | ANON | Tunnel secret key |
tower_server | no | tower.cruma.io:443 | Control plane endpoint |
profile | no | — | Named profile to scope cached identity |
temp | no | false | Use a temporary identity (fresh FQDN each run) |
local_only | no | false | Disable cloud tunnel; only local listeners are active |
processes | no | [] | Hosted processes supervised by the agent |
listeners | no | [] | Local listeners |
Notes:
profileandtempare mutually exclusive.- Changes to
tower_server,profile, ortemprequire a restart to take effect. - Other changes (credentials, backends, frontends) are hot-reloaded when the config file is saved.