CLI guide
Docs
appfwd gives a locally running app a public URL. The client opens one TLS tunnel to the server; traffic to your subdomain is forwarded to a local port.
Quickstart
appfwd 3000 is shorthand for appfwd forward -p 3000. You get a public https://<subdomain> URL forwarding to localhost:3000. In an interactive terminal the CLI shows a live request view — and a domain picker first, when you have custom domains and didn't pass -d. Scripts and pipes always get plain line output. appfwd update self-updates the binary.
Auth & sessions
login stores a session cookie in the OS keychain (-k uses a file instead). For CI and headless machines, appfwd authtoken afwd_... saves an API token to the config file — it authenticates check and domains, but opening tunnels still requires a login session: run appfwd login on the machine that forwards.
Forwarding
-d picks a custom subdomain; without it the server assigns a random one. appfwd domains lists your custom domains and their verification status.
Headers & security
-H rewrites the Host header sent to your local app (for name-based virtual hosts) — it does not change where traffic is sent. --request-header and --response-header set (Key: Value) or remove (Key:) headers and are repeatable. -b user:pass requires basic auth from visitors; --allow-ip restricts visitors to IPs/CIDRs. If your local app only speaks HTTPS, add --local-https (self-signed loopback certs are trusted).
Traffic limits: --rate-limit N[/window] caps visitor requests (over-limit gets 429 with Retry-After; scope with --rate-per ip|total), --max-body rejects larger request bodies, --max-conns caps concurrent in-flight requests. --path-rule overrides any of these — plus auth and OAuth gating — per path prefix; longest matching prefix wins. Everything here can also be edited live from the dashboard while the tunnel runs (except path rules with custom credentials on a leased subdomain — those are set via the CLI at connect time).
Inspector & capture
--inspect runs a local, loopback-only web inspector (default http://127.0.0.1:4040) showing the last 500 requests with headers and bodies (--inspect-body-limit caps stored body bytes; 0 = headers only). -c additionally stores the session's traffic in your account for the dashboard's request history. The inspector page has a live toggle that flips capture on or off mid-session without restarting the tunnel.
TCP tunnels
--tcp exposes the local port as a raw TCP tunnel — the server assigns a public port and forwards bytes unmodified (databases, SSH, game servers). There is no HTTP layer: --basic-auth and any active traffic-policy flag (rate limit, body cap, non-zero max-conns, path rules) are refused with an error, while -H, capture, header rewrites and the inspector are ignored. --allow-ip still works — use it to protect TCP tunnels.
Flag reference
| Flag | Default | Description |
|---|---|---|
| -p, --port | 8080 | Local port to forward |
| -d, --domain | — | Custom subdomain (default: auto-assigned) |
| -H, --host | — | Rewrite the Host header sent to your local app; the tunnel still dials the local port |
| -b, --basic-auth | — | Require basic auth from visitors (user:pass) |
| -c, --capture | false | Store this session's traffic in your account |
| --request-header | — | Rewrite a request header ('Key: Value' set, 'Key:' remove; repeatable) |
| --response-header | — | Rewrite a response header (same syntax; repeatable) |
| --allow-ip | — | Restrict visitors to these IPs/CIDRs (repeatable or comma-separated) |
| --local-https | false | Dial the local app over HTTPS (self-signed loopback certs trusted) |
| --rate-limit | — | Limit visitor requests, N or N/window (e.g. 60/1m); over-limit gets 429 |
| --rate-per | ip | Rate-limit scope: 'ip' (per visitor IP) or 'total' |
| --max-body | — | Reject request bodies larger than this (bytes, or k/m/g suffix) |
| --max-conns | 0 | Limit concurrent in-flight requests (0 = unlimited) |
| --path-rule | — | Per-path policy override, repeatable: "<prefix>:public|key=value,..." |
| --inspect | false | Run the local web inspector (loopback only) |
| --inspect-addr | 127.0.0.1:4040 | Inspector listen address |
| --inspect-body-limit | 65536 | Max stored body bytes per request/response (0 = headers only) |
| --tcp | false | Raw TCP tunnel instead of HTTP; incompatible with --basic-auth and active traffic-policy flags |
Global: -k, --disable-keychain stores credentials in a file instead of the OS keychain.
Configuration
Precedence: environment variables > config file (~/.config/appfwd/config.yml, override with $APPFWD_CONFIG) > defaults. Forward flags configure the tunnel itself; they don't override these connection settings.
| Variable | Default | Description |
|---|---|---|
| CLI_ENDPOINT | https://ifup.dev | API endpoint URL |
| CLI_PROXY_ENDPOINT | go.direct.ifup.dev:30000 | Tunnel (TCP proxy) address |
| CLI_AUTHTOKEN | — | API token for check/domains; forward still needs a login session |
| CLI_PROXY_TLS | true | TLS on the tunnel connection |
| CLI_PROXY_TLS_INSECURE | false | Skip TLS verification (self-signed dev servers) |
| SESSION_COOKIE_NAME | appfwd_session | Session cookie name (must match the server) |
| APPFWD_CONFIG | — | Config file path override |
Config file keys: endpoint, proxy_endpoint, cookie_name, authtoken. appfwd environment exports the resolved configuration (minus the API token) to a .env file in the current directory.