ember [flags]
| Flag | Type | Default | Description |
|---|---|---|---|
--addr |
string (repeatable) | http://localhost:2019 |
Caddy admin API address (http://, https://, or unix//path). Repeatable in --daemon, --json, status, and wait modes to monitor multiple instances. Supports name=url aliases and per-instance suffixes (,ca=PATH, ,cert=PATH, ,key=PATH, ,insecure, ,interval=DURATION); see Multi-instance. |
-i, --interval |
duration | 1s |
Polling interval |
--timeout |
duration | 0 (none) |
Global timeout. Applies to all modes and subcommands. 0 means no timeout. |
--slow-threshold |
int | 500 |
Slow request threshold in milliseconds. Requests above this are highlighted yellow; above 2x are red. |
--frankenphp-pid |
int | 0 (auto) |
FrankenPHP PID. Auto-detected if not set. |
--json |
bool | false |
JSON output mode (streaming JSONL). See JSON Output. |
--once |
bool | false |
Output a single snapshot and exit. Requires --json. See JSON Output. |
--expose |
string | (none) | Start Prometheus metrics endpoint on this address (e.g. :9191). See Prometheus Export. |
--daemon |
bool | false |
Headless mode: no TUI. Requires --expose. See Prometheus Export. |
--metrics-prefix |
string | (none) | Prefix for exported Prometheus metric names. See Prometheus Export. |
--log-format |
string | text |
Log format for daemon/json modes (text or json). JSON format is suitable for log aggregation systems. |
--ca-cert |
string | (none) | Path to CA certificate for TLS verification |
--client-cert |
string | (none) | Path to client certificate for mTLS |
--client-key |
string | (none) | Path to client private key for mTLS |
--insecure |
bool | false |
Skip TLS certificate verification |
--metrics-auth |
string | (none) | Basic auth for the metrics endpoint (user:password). Requires --expose. See Prometheus Export. |
--log-listen |
string | (auto) | Bind a TCP listener at this address (e.g. :9210) and ask Caddy to push its logs to it via two hot-registered sinks (access + runtime). Required when Caddy is on a remote host; auto-bound on a free loopback port otherwise. See Logs. |
--no-color |
bool | false |
Disable colors. Also enabled by the NO_COLOR env var (see no-color.org). |
--version |
Print version and exit |
Some flags can be set via environment variables. Explicit flags always take precedence over environment variables.
| Variable | Flag | Example |
|---|---|---|
EMBER_ADDR |
--addr |
EMBER_ADDR=http://caddy:2019, EMBER_ADDR=unix//run/caddy/admin.sock, or semicolon-separated for multi-instance: EMBER_ADDR=web1=https://a;web2=https://b. The semicolon (not a comma) is the address separator so each entry can carry comma-separated TLS suffixes (e.g. web1=https://a,ca=/p/ca1.pem;web2=https://b,insecure). |
EMBER_INTERVAL |
--interval |
EMBER_INTERVAL=5s |
EMBER_EXPOSE |
--expose |
EMBER_EXPOSE=:9191 |
EMBER_METRICS_PREFIX |
--metrics-prefix |
EMBER_METRICS_PREFIX=myapp |
EMBER_METRICS_AUTH |
--metrics-auth |
EMBER_METRICS_AUTH=admin:secret |
EMBER_LOG_LISTEN |
--log-listen |
EMBER_LOG_LISTEN=:9210 |
This is especially useful in container deployments where flags are less convenient than environment variables. Using EMBER_METRICS_AUTH is recommended over the flag to avoid exposing credentials in ps output.
# Default: connect to localhost:2019
ember
# Connect to a remote Caddy instance
ember --addr http://prod:2019
# Connect via Unix socket
ember --addr unix//run/caddy/admin.sock
# Pipe-friendly JSON output
ember --json
# Single JSON snapshot and exit
ember --json --once
# JSON output at a slower rate
ember --json --interval 5s
# TUI with Prometheus metrics on port 9191
ember --expose :9191
# Headless metrics exporter (no TUI)
ember --expose :9191 --daemon
# Stricter slow-request highlighting
ember --slow-threshold 200
# Prefixed Prometheus metrics
ember --expose :9191 --metrics-prefix myapp
# Explicitly specify a FrankenPHP PID
ember --frankenphp-pid 42A single ember --daemon (or ember --json) process can poll several Caddy instances and aggregate their metrics behind one Prometheus endpoint. This keeps the sidecar count low when running a fleet of small services.
# Explicit aliases
ember --daemon --expose :9191 \
--addr web1=https://web1.fr \
--addr web2=https://web2.fr
# Without aliases the host is slugified (web1.fr -> web1_fr)
ember --daemon --expose :9191 \
--addr https://web1.fr \
--addr https://web2.fr
# Semicolon-separated env var (each entry may carry comma-separated TLS suffixes)
EMBER_ADDR='web1=https://a;web2=https://b' ember --daemon --expose :9191When two or more --addr values are supplied, every emitted Prometheus metric (except ember_build_info) gains an ember_instance="<name>" label. With a single --addr the output is unchanged: no extra label.
The interactive TUI stays single-instance by design. Watching several Caddy hosts at the same time is what --daemon (Prometheus aggregation, then visualised with Grafana or any compatible dashboard) and --json (one JSONL line per instance per tick, ready for downstream tooling) are for. Passing repeated --addr to the default TUI mode fails fast with an explicit error pointing at those modes.
Each --addr value can carry its own TLS knobs as comma-separated suffixes after the URL. Any field omitted falls back to the corresponding global flag (--ca-cert, --client-cert, --client-key, --insecure).
| Suffix | Equivalent global flag | Notes |
|---|---|---|
,ca=PATH |
--ca-cert |
CA certificate used to verify the server |
,cert=PATH |
--client-cert |
Client certificate (mTLS); must be paired with key= |
,key=PATH |
--client-key |
Client private key (mTLS); must be paired with cert= |
,insecure or ,insecure=true|false |
--insecure |
Skip TLS verification; explicit =false overrides a global --insecure |
ember --daemon --expose :9191 \
--addr web1=https://a,ca=/etc/ca1.pem \
--addr web2=https://b,ca=/etc/ca2.pemOn SIGHUP, each instance re-reads its own certificate files independently, so per-instance certs can be rotated in place without restarting Ember.
Each --addr can override the global --interval with a ,interval=DURATION suffix. This matters for fleets where some instances are remote and benefit from a slower cadence without raising the global default.
ember --daemon --expose :9191 \
--addr web1=https://web1.fr \
--addr remote=https://far.example,interval=10sActive in --daemon and --json modes (each instance polls its own ticker). The /healthz and /healthz/<name> staleness threshold is computed per-instance, so a slow instance is not flagged stale just because its cadence is slower than the global default. status and wait keep using the global interval.
Constraints:
- File paths in suffixes cannot contain a literal comma; the comma is reserved as the suffix separator.
- TLS suffixes are rejected on
unix//addresses (no TLS over a Unix socket). - Per-instance
interval=is allowed onunix//addresses (it is not a TLS option). - Per-instance
interval=must be at least100ms. When--timeoutis set, it must be at least the largest effective polling interval (global or per-instance). --daemon,--json,status,wait,diff, andinit(with-y) accept multi-instance input. The TUI default mode is single-instance by design and refuses repeated--addrwith an explicit error pointing at--daemonand--json.- Instance names must match
[a-zA-Z_][a-zA-Z0-9_]*(Prometheus label rules: letters, digits and underscores only — no hyphens or dots). With more than one address, slugified names that start with a digit (typical for raw IPv4 hosts) require an explicitname=urlalias. --frankenphp-pidis ignored when--addris repeated. Local instances (localhost,127.0.0.1,::1orunix//) get per-instanceprocess_cpu_percent/process_rss_bytesvia auto-detection on the admin endpoint; remote instances silently fall back to theprocess_*metrics exposed by Caddy.- Plugins are skipped in multi-instance mode unless they opt in by implementing
plugin.MultiInstancePlugin. Non-aware plugins are disabled with a startup warning. See the Plugin Development Guide for the multi-instance contract.
See Prometheus Export for the resulting metric labels and /healthz body.
Checks that Caddy is properly configured for Ember and offers to enable HTTP metrics via the admin API if they are missing. Does not modify any files on disk.
What it checks:
- Admin API is reachable
- HTTP servers are configured
- HTTP metrics directive is enabled
- FrankenPHP presence, threads, and workers
- Metrics are actually flowing
What it can do:
- Enable the
metricsdirective viaPOST /config/apps/http/metrics(no Caddy restart needed)
Examples:
ember init
ember init --addr https://prod:2019 --ca-cert ca.pem
ember init -y # skip confirmation prompts
ember init -yq # skip prompts, suppress output
ember init -y --addr web1=https://a --addr web2=https://b # multi-instanceWith multiple --addr values, the checklist is run against every instance, output is grouped per instance under a --- <name> --- header sorted by name, and the exit code is non-zero if any instance fails. Interactive prompts are disabled in multi-instance mode: --yes (-y) is required, otherwise the command errors out.
| Flag | Type | Default | Description |
|---|---|---|---|
-y, --yes |
bool | false |
Skip confirmation prompts (required when multiple --addr are provided) |
-q, --quiet |
bool | false |
Suppress output (errors still reported via exit code) |
Prints the current version. With --check, queries the GitHub Releases API to see if a newer version is available.
Examples:
ember version
ember version --check| Flag | Type | Default | Description |
|---|---|---|---|
--check |
bool | false |
Check for newer version on GitHub |
One-line health check of Caddy. Performs two fetches separated by the polling interval to compute derived metrics, then prints a compact status line and exits.
Exit code 0 means Caddy is reachable, 1 means unreachable.
Output format:
Caddy OK | 5 hosts | 450 rps | P99 12ms | CPU 3.2% | RSS 48MB | up 3d 2h
With FrankenPHP:
Caddy OK | 5 hosts | 450 rps | P99 12ms | CPU 3.2% | RSS 48MB | up 3d 2h | FrankenPHP 8/20 busy | 2 workers
If Caddy is unreachable:
Caddy UNREACHABLE | http://localhost:2019
Examples:
ember status
ember status --json
ember status --json | jq .rps
ember status --addr http://prod:2019
ember status --interval 2s
ember status --addr web1=https://a --addr web2=https://b # multi-instanceWhen --addr is repeated, ember status checks every instance in parallel and emits one block per instance, sorted alphabetically by name. Exit code is 0 only when all instances are reachable.
Text output:
[web1] Caddy OK | 5 hosts | 450 rps | P99 12ms | CPU 3.2% | RSS 48MB | up 3d 2h
[web2] Caddy UNREACHABLE | https://web2.fr
JSON output (--json):
{
"status": "degraded",
"instances": [
{"name": "web1", "addr": "https://web1.fr", "status": "ok", "rps": 450, "cpuPercent": 3.2, "rssBytes": 50331648, "p99": 12.3},
{"name": "web2", "addr": "https://web2.fr", "status": "unreachable", "rps": 0, "cpuPercent": 0, "rssBytes": 0}
]
}The top-level status is the worst of: ok (every instance reachable) < degraded (mixed) < unreachable (every instance down). FrankenPHP fields are emitted per instance when detected.
With --json (single-instance mode), the output is a JSON object:
{
"status": "ok",
"hosts": 5,
"rps": 450,
"p99": 12.3,
"cpuPercent": 3.2,
"rssBytes": 50331648,
"uptime": "3d 2h",
"frankenphp": { "busy": 8, "total": 20, "workers": 2 }
}| Flag | Type | Default | Description |
|---|---|---|---|
--json |
bool | false |
Output status as JSON |
The --addr, --interval, and --frankenphp-pid flags are also available.
Blocks until the Caddy admin API is reachable, then exits with code 0. If --timeout is set and Caddy is still unreachable, exits with code 1.
With repeated --addr, ember wait blocks until every instance is reachable (strict CI semantics). Pass --any to return as soon as the first one responds. On timeout, the error names the lagging instance(s).
Examples:
ember wait
ember wait --timeout 30s
ember wait -q --timeout 10s && ./deploy.sh
ember wait --addr http://prod:2019 && ember status
docker compose up -d && ember wait && ./deploy.sh
ember wait --addr web1=https://a --addr web2=https://b --timeout 30s
ember wait --any --addr http://primary --addr http://fallback| Flag | Type | Default | Description |
|---|---|---|---|
-q, --quiet |
bool | false |
Suppress output (exit code only) |
--any |
bool | false |
Return as soon as any instance is reachable (default: wait for all) |
The --addr, --interval, and --timeout flags are also available. --addr may be repeated to wait on multiple instances (with optional name=url aliases).
Compares two JSON or JSONL snapshots produced by ember --json --once and shows the deltas for key metrics. Useful for validating deployments and benchmarks.
Exit code 0 means no regressions detected, 1 means regressions found (>10% degradation on latency, error rate, or CPU; >10% drop on RPS).
Examples:
ember --json --once > before.json
# ... deploy or benchmark ...
ember --json --once > after.json
ember diff before.json after.jsonSample output:
Global
Requests 1720 -> 2023 +17.6%
Avg (cumul.) 76.9ms -> 77.7ms +1.0%
Errors 0 -> 0 =
In-flight 3.0 -> 1.0 -66.7%
CPU 0 -> 0 =
RSS 39.5MB -> 39.5MB =
Per-host changes
api
In-flight 3.0 -> 1.0 -66.7%
app
In-flight 1.0 -> 0 -100.0%
No regressions detected
Multi-instance input:
When a snapshot was produced with repeated --addr (multi-instance JSONL with one line per instance per tick), ember diff groups lines by the instance field, keeps the last entry per instance in each file, and emits one diff block per instance. The exit code is non-zero if any single instance regresses.
ember --json --once --addr web1=https://web1.fr --addr web2=https://web2.fr > before.jsonl
# ... deploy ...
ember --json --once --addr web1=https://web1.fr --addr web2=https://web2.fr > after.jsonl
ember diff before.jsonl after.jsonl== web1 ==
Global
RPS 100/s -> 105/s +5.0%
Avg latency 20.0ms -> 19.0ms -5.0%
...
== web2 ==
Global
RPS 200/s -> 210/s +5.0%
Avg latency 15.0ms -> 14.0ms -6.7%
...
No regressions detected
| Key | Action |
|---|---|
Up / Down / j / k |
Move cursor |
Enter / Right / l |
Open detail panel / expand node (Caddy Config tab) |
Left / h |
Collapse node (Caddy Config tab) |
Esc |
Close panel / clear search / go back |
Tab / Shift+Tab |
Switch tab |
1 / 2 / 3 |
Jump to tab |
t then 1..9 |
Tab-select prefix: arm tab-select mode, next digit switches tab (Esc cancels) |
Home / End |
Jump to first / last item |
PgUp / PgDn |
Page up / page down |
| Key | Action | Context |
|---|---|---|
s / S |
Cycle sort field forward / backward | Caddy / FrankenPHP tabs |
p |
Pause / resume polling | Any view |
/ |
Enter filter / search mode | Any tab |
e / E |
Expand / collapse all nodes | Config tab |
n / N |
Jump to next / previous search match | Config tab |
r |
Refresh config / restart workers | Config tab / FrankenPHP tab |
g |
Toggle full-screen graphs | Any view |
? |
Toggle help overlay | Any view |
q |
Quit | Any view |
ember completion bash > /etc/bash_completion.d/emberember completion zsh > "${fpath[1]}/_ember"ember completion fish > ~/.config/fish/completions/ember.fish