Documentation
This reference covers every flag and configuration key Ferrule supports. If it isn't listed here, it doesn't exist.
Overview
Ferrule is one binary that does two things at once: it's a reverse proxy that terminates TLS for hostnames you make up (like api.local), and it's a process supervisor that can start and restart your services directly. Both are driven by a single ferrule.toml file that lives in your project.
You don't have to use both halves. A service can be proxy_to only (something you start yourself), command only (something Ferrule starts, with no HTTP host attached), or both — Ferrule starts the process and routes a hostname to it.
Installation
Pick your platform. Every path ends with a single ferrule binary on your PATH.
brew install ferrule-dev/tap/ferrule
ferrule trust # installs the local CA into your Keychain
curl -fsSL https://get.syntaxnet.org/install.sh | sh
ferrule trust # uses ca-certificates / NSS depending on your distro
NoteThe install script writes to ~/.local/bin and never touches system directories. ferrule trust is the one command in the whole tool that asks for sudo.
scoop bucket add ferrule https://github.com/ferrule-dev/scoop-bucket
scoop install ferrule
ferrule trust # run from an elevated prompt
# requires Rust 1.75+
cargo install ferrule-proxy
ferrule trust
Quick start
-
Scaffold a config. Run this inside your project — it writes a
ferrule.tomlwith one example service pointing at127.0.0.1:3000.ferrule init -
Edit it. Add every service you run locally, following the configuration reference below.
-
Trust the local CA, once per machine.
ferrule trust -
Start everything.
ferrule upferrule — 3 services, https on :443 name host upstream status uptime web app.local 127.0.0.1:5173 up 00:04:12 api api.local 127.0.0.1:4000 up (2xx) 00:04:12 worker — node worker.js up 00:04:09 press q to quit · l to view logs · r to reload config Open
https://app.localin a browser. No certificate warning.
Configuration reference
One [server] table, and one [[service]] block per service.
[server]
| Key | Type | Default | Description |
|---|---|---|---|
bind | string | "127.0.0.1" | Address Ferrule listens on. Use "0.0.0.0" to reach it from another device on your LAN. |
http_port | integer | 80 | Plain HTTP port; requests are redirected to HTTPS unless a service opts out. |
https_port | integer | 443 | TLS port serving certificates from the local CA. |
local_ca | boolean | true | Generate and use a local certificate authority. Set false to serve plain HTTP only. |
log_level | string | "info" | One of trace, debug, info, warn, error. |
log_format | string | "text" | "text" for the dashboard, or "json" for structured lines suitable for piping elsewhere. |
[[service]]
| Key | Type | Default | Description |
|---|---|---|---|
name | string | required | Unique identifier used by ferrule status and ferrule logs. |
host | string | none | Hostname to route, e.g. "api.local". Omit for a supervised process with no HTTP endpoint. |
proxy_to | string | none | "ip:port" upstream. Required if host is set and command is not. |
path_prefix | string | "/" | Route only requests under this prefix to this service — lets several services share one host. |
command | string | none | Shell command Ferrule spawns and supervises. Can be combined with host + proxy_to. |
workdir | string | "." | Working directory for command. |
restart | string | "never" | One of never, on-failure, always. |
backoff | string | "1s..30s" | Restart delay range; doubles on each consecutive failure up to the max. |
health_check | string | none | HTTP path polled every 2s once the port is open. Determines the "up (2xx)" status. |
env | table | none | Extra environment variables passed to command. |
CLI reference
| Command | Description |
|---|---|
ferrule init | Writes a starter ferrule.toml in the current directory. |
ferrule check | Validates the config and flags port conflicts without starting anything. |
ferrule up | Starts the proxy and every supervised process, with a live status dashboard in the terminal. |
ferrule up -d | Same, detached to the background. Writes a PID file to .ferrule/ferrule.pid. |
ferrule status | Prints a one-shot table of every service's host, upstream, health and uptime. |
ferrule logs <name> [-f] | Prints, or follows with -f, the captured output of one supervised process. |
ferrule reload | Re-reads ferrule.toml and issues certificates for any new hosts, without dropping connections. |
ferrule trust | Installs the local CA root into the OS trust store. The only command needing elevated privileges. |
ferrule doctor | Diagnoses port conflicts, an expired local CA, stale PID files, and unresolvable hosts. |
Routing rules
Routing is host-first: Ferrule reads the Host header (or the TLS SNI, for HTTPS) and matches it against every configured host. Matching is exact — api.local does not also match www.api.local. If you need several, list several services.
Two services can share a host if their path_prefix values don't overlap. The longest matching prefix wins, so a more specific rule can sit alongside a catch-all:
[[service]]
name = "app-shell"
host = "app.local"
path_prefix = "/"
proxy_to = "127.0.0.1:5173"
[[service]]
name = "app-api"
host = "app.local"
path_prefix = "/api"
proxy_to = "127.0.0.1:4000"
Requests to app.local/api/orders go to app-api; everything else on app.local goes to app-shell.
Local TLS
The first time ferrule up runs with local_ca = true, it generates a root key and certificate under ~/.config/ferrule/ca (Linux/macOS) or %APPDATA%\ferrule\ca (Windows) — never shared, never uploaded. ferrule trust adds that root to your OS trust store: Keychain on macOS, the NSS database and ca-certificates on Linux, the Windows Certificate Store on Windows.
From then on, Ferrule issues a short-lived leaf certificate for every configured host, valid 90 days and renewed automatically on ferrule reload. Because each machine has its own root, trusting your CA never lets you (or anyone) intercept another machine's traffic — teammates each run ferrule trust once, locally.
Process supervision
A service with a command is started by Ferrule as a child process. Its fate is controlled by restart:
never— run once; if it exits, it stays stopped and shows asexitedin the dashboard.on-failure— restart only on a non-zero exit code or a failinghealth_check.always— restart on any exit, including a clean one.
Restarts are spaced by backoff, a "min..max" range: the delay starts at min and doubles on each consecutive failure until it hits max, then holds there. A process that stabilizes resets the delay back to min.
If health_check is set, the service isn't marked up until that path returns a 2xx — useful for a Node or Rails process that opens its port before it's actually ready to serve.
Observability
ferrule status prints the same table the dashboard shows, for scripting:
$ ferrule status
name host upstream status uptime
web app.local 127.0.0.1:5173 up 01:12:03
api api.local 127.0.0.1:4000 up (2xx) 01:12:03
worker — node worker.js restarting 00:00:04The same data is available inside the browser at https://ferrule.local/status — a host Ferrule reserves for itself and answers on every machine it runs on. Set log_format = "json" in [server] to get structured, line-delimited logs instead of the human-readable default, suitable for piping into whatever you already use to read logs.
Troubleshooting
ferrule trust and fully quit and reopen your browser — some browsers cache trust decisions for the lifetime of the process, not just the tab.ferrule doctor — it prints the PID and name of whatever is already bound, commonly another proxy or, on Windows, the World Wide Web Publishing Service. Stop that, or move Ferrule to a different http_port/https_port.ferrule logs <name> first. The usual causes are a wrong workdir, or a dev server that needs an explicit host flag to bind past 127.0.0.1. backoff keeps a broken loop from spinning a CPU core while you fix it.sudo setcap 'cap_net_bind_service=+ep' $(which ferrule), or set http_port/https_port above 1024. ferrule doctor prints both options with the exact path to your binary.proxy_to accepts any reachable ip:port, including one on your LAN. The local CA still only issues certificates for hosts you've explicitly configured.