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

# get the binary → ifup.dev/download
$ appfwd login
$ appfwd 3000

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

$ appfwd register <user> <pass> <code>
$ appfwd login
$ appfwd check
$ appfwd logout

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

$ appfwd forward -p 8080 -d myapp
$ appfwd domains

-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

$ appfwd forward -p 8080 -H app.local \
--request-header 'X-Forwarded-Proto: https' \
--response-header 'Server:' \
-b user:pass --allow-ip 203.0.113.0/24

-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).

$ appfwd forward -p 8080 --rate-limit 60/1m --max-body 10m \
--path-rule '/hooks/stripe:public' \
--path-rule '/admin:auth=root:changeme,rate-limit=10/1m'

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

$ appfwd forward -p 3000 --inspect -c

--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

$ appfwd forward -p 5432 --tcp

--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

appfwd forward flags
FlagDefaultDescription
-p, --port8080Local port to forward
-d, --domainCustom subdomain (default: auto-assigned)
-H, --hostRewrite the Host header sent to your local app; the tunnel still dials the local port
-b, --basic-authRequire basic auth from visitors (user:pass)
-c, --capturefalseStore this session's traffic in your account
--request-headerRewrite a request header ('Key: Value' set, 'Key:' remove; repeatable)
--response-headerRewrite a response header (same syntax; repeatable)
--allow-ipRestrict visitors to these IPs/CIDRs (repeatable or comma-separated)
--local-httpsfalseDial the local app over HTTPS (self-signed loopback certs trusted)
--rate-limitLimit visitor requests, N or N/window (e.g. 60/1m); over-limit gets 429
--rate-peripRate-limit scope: 'ip' (per visitor IP) or 'total'
--max-bodyReject request bodies larger than this (bytes, or k/m/g suffix)
--max-conns0Limit concurrent in-flight requests (0 = unlimited)
--path-rulePer-path policy override, repeatable: "<prefix>:public|key=value,..."
--inspectfalseRun the local web inspector (loopback only)
--inspect-addr127.0.0.1:4040Inspector listen address
--inspect-body-limit65536Max stored body bytes per request/response (0 = headers only)
--tcpfalseRaw 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.

Client environment variables
VariableDefaultDescription
CLI_ENDPOINThttps://ifup.devAPI endpoint URL
CLI_PROXY_ENDPOINTgo.direct.ifup.dev:30000Tunnel (TCP proxy) address
CLI_AUTHTOKENAPI token for check/domains; forward still needs a login session
CLI_PROXY_TLStrueTLS on the tunnel connection
CLI_PROXY_TLS_INSECUREfalseSkip TLS verification (self-signed dev servers)
SESSION_COOKIE_NAMEappfwd_sessionSession cookie name (must match the server)
APPFWD_CONFIGConfig 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.

Docs · appfwd