Port Management & Reverse Proxy
Pitchfork provides smart port management and an optional reverse proxy that gives your daemons stable, human-friendly URLs.
Port Assignment
Configure the ports your daemon expects to use:
[daemons.api]
run = "node server.js"
port = 3000For multiple ports:
[daemons.multi]
run = "./start.sh"
port = [8080, 8443]Pitchfork checks if the port is available before starting, injects PORT=3000 into the daemon's environment, and fails with a clear error if the port is already in use.
Auto Port Bumping
When a port is occupied, enable bump to automatically find the next available port:
[daemons.api]
run = "node server.js"
port = { expect = [3000], bump = 10 } # bump up to 10 timesUsing bump = true enables unlimited bump attempts:
[daemons.api]
run = "node server.js"
port = { expect = [3000], bump = true }The daemon receives the actual allocated port via $PORT.
Active Port Tracking
After a daemon starts, pitchfork detects the port the process is actually listening on. This detected port is the source of truth for the reverse proxy.
Reverse Proxy
The reverse proxy routes requests from stable URLs to the daemon's actual port.
Why Use the Proxy?
Without the proxy, you need to know the actual port your daemon is running on — which can change if ports are auto-bumped. With the proxy:
https://myapp.localhost → http://localhost:3001The URL stays the same even if the port changes. This is especially useful for:
- Sharing URLs with teammates
- AI agents that need stable endpoints
- Browser bookmarks
- Webhook configurations
Quick start
- Enable the proxy in
pitchfork.toml:
[settings.proxy]
enable = true- Start the supervisor:
sudo pitchfork supervisor start --force # port 80 or 443 requires sudo- Add a slug in the global config:
pitchfork proxy add api
# or with explicit dir and daemon name:
pitchfork proxy add api --dir /path/to/project --daemon serverThis registers the slug in ~/.config/pitchfork/config.toml:
[slugs]
api = { dir = "/path/to/project", daemon = "server" }- Start the daemon:
pitchfork start api- Open the proxy URL:
open https://api.localhostIf this is your first time using the auto-generated HTTPS certificate, trust it once:
pitchfork proxy trust # On Linux, run the trust step with `sudo`Slugs
Slugs are defined in the global config (~/.config/pitchfork/config.toml) under [slugs]. Each slug maps to a project directory and (optionally) a specific daemon name:
# ~/.config/pitchfork/config.toml
[slugs]
api = { dir = "/home/user/my-api", daemon = "server" }
frontend = { dir = "/home/user/my-app", daemon = "dev" }
# If daemon name matches slug, it can be omitted:
docs = { dir = "/home/user/docs-site" } # defaults daemon = "docs"URL format
Proxy URLs use this shape:
https://<slug>.<tld>Examples:
https://myapp.localhost— standard HTTPS port 443, by defaulthttps://api.localhost:7777— custom port
Managing slugs
# Add a slug for current directory
pitchfork proxy add myapp
# Add a slug with explicit dir and daemon
pitchfork proxy add api --dir /home/user/api --daemon server
# Remove a slug
pitchfork proxy remove api
# or: pitchfork proxy rm api
# Show all slugs and their status
pitchfork proxy statusStandard Ports (80/443)
To use standard HTTP/HTTPS ports without the port number in URLs:
http://api.localhost (port 80)
https://api.localhost (port 443)Binding to Privileged Ports
Ports below 1024 require elevated privileges on Unix systems. You must start the supervisor with sudo:
# HTTP on port 80
sudo PITCHFORK_PROXY_PORT=80 PITCHFORK_PROXY_HTTPS=false pitchfork supervisor start
# HTTPS on port 443 (default)
sudo pitchfork supervisor startOr in pitchfork.toml:
[settings.proxy]
enable = true
port = 80 # requires: sudo pitchfork supervisor start
https = falseRequires sudo
Binding to ports below 1024 (including 80 and 443) requires the supervisor to be started with sudo. The proxy will fail to start if it cannot bind to the configured port.
HTTPS Support
Auto-Generated Certificate
When proxy.https = true (the default) and no certificate is configured, pitchfork auto-generates a self-signed certificate:
[settings.proxy]
enable = true
# https = true is the default
# port = 443 is the defaultThe certificate is stored in $PITCHFORK_STATE_DIR/proxy/cert.pem.
Trusting the Certificate
Install the auto-generated certificate into your system trust store:
pitchfork proxy trustOn macOS, this installs the certificate into your user login keychain — no sudo required.
On Linux, this requires sudo:
sudo pitchfork proxy trustCustom Certificate
Provide your own certificate (e.g., from mkcert or Let's Encrypt):
[settings.proxy]
enable = true
https = true
tls_cert = "/path/to/cert.pem"
tls_key = "/path/to/key.pem"Using mkcert for a locally-trusted certificate:
# Install mkcert and set up local CA
mkcert -install
# Generate certificate for your TLD
mkcert "*.localhost" localhost 127.0.0.1
# Configure pitchfork to use it[settings.proxy]
enable = true
https = true
tls_cert = "/path/to/_wildcard.localhost+2.pem"
tls_key = "/path/to/_wildcard.localhost+2-key.pem"Custom TLD
Use a custom TLD instead of localhost:
[settings.proxy]
enable = true
tld = "test"With the default proxy.sync_hosts = true, pitchfork keeps registered slugs synced into /etc/hosts, so you usually do not need to set up dnsmasq or any other wildcard DNS service just to use a custom TLD.
For example, if you register these slugs:
pitchfork proxy add api
pitchfork proxy add docspitchfork will maintain matching /etc/hosts entries such as:
127.0.0.1 api.test
127.0.0.1 docs.testThis works for registered slugs only. It is not wildcard DNS for arbitrary *.test names.
If pitchfork cannot write /etc/hosts, you still need to provide DNS resolution yourself, for example with dnsmasq or platform-specific resolver configuration.
Wildcard Subdomain Matching
When proxy.wildcard = true (the default), the proxy matches not only exact slug hostnames but also their subdomains. For example, with slug myapp registered, both myapp.localhost and tenant.myapp.localhost route to the same daemon.
However, whether the subdomain actually resolves depends on the TLD:
| TLD | Exact slug (myapp.localhost) | Wildcard subdomain (tenant.myapp.localhost) |
|---|---|---|
.localhost (default) | Browser auto-resolves; /etc/hosts optional | Browser auto-resolves; wildcard routing works |
Custom (.test etc.) | /etc/hosts entry makes it resolvable | /etc/hosts cannot cover; needs dnsmasq |
With the default .localhost TLD, wildcard subdomains work out of the box in Chrome and Firefox (which auto-resolve .localhost per RFC 2606). Safari does not auto-resolve .localhost subdomains, so wildcard subdomains will not resolve unless you configure a local DNS resolver such as dnsmasq.
To set up wildcard DNS resolution for a custom TLD, install dnsmasq and add a wildcard entry:
# /etc/dnsmasq.d/pitchfork (or equivalent)
address=/test/127.0.0.1Then point your system resolver at the local dnsmasq instance. On macOS, you can create /etc/resolver/test:
nameserver 127.0.0.1
port 53LAN Mode
LAN mode lets other devices on your local network (phones, tablets, other computers) access your daemons through the proxy. Instead of using .localhost (which only resolves on the host machine), LAN mode switches to the .local TLD and publishes slug hostnames via mDNS.
Quick start
- Enable LAN mode in
pitchfork.toml:
[settings.proxy]
enable = true
lan = true- Start the supervisor:
sudo pitchfork supervisor start --force- Open the proxy URL from another device on the same network:
https://myapp.localHow it works
When LAN mode is enabled:
- The TLD is forced to
.local(mDNS requirement) - The proxy binds to
0.0.0.0instead of127.0.0.1(overridable viaproxy.host) - Each registered slug is published as an mDNS address record (
myapp.local → 192.168.1.42) - Your LAN IP is auto-detected; if it changes, mDNS records are re-published
Pinning the LAN IP
By default, pitchfork auto-detects your LAN IP. To pin a specific address:
[settings.proxy]
enable = true
lan_ip = "192.168.1.42"Setting lan_ip implies lan = true, so you can omit the lan flag.
HTTPS on LAN
Other devices need to trust the pitchfork CA certificate to use HTTPS. Run pitchfork proxy trust on each device, or disable HTTPS for simplicity:
[settings.proxy]
enable = true
lan = true
https = false
port = 80Proxy Commands
# Show all registered slugs and their status
pitchfork proxy status
# Add a slug for the current directory
pitchfork proxy add myapp
# Add with explicit project dir and daemon name
pitchfork proxy add api --dir /path/to/project --daemon server
# Remove a slug
pitchfork proxy remove api
# Install TLS certificate into system trust store
pitchfork proxy trust
# Install a custom certificate
pitchfork proxy trust --cert /path/to/cert.pemAuto-Start
When you visit a proxy URL for a daemon that isn't running, pitchfork can automatically start it for you. Instead of a 502 Bad Gateway error, you'll see a "Starting…" page that refreshes every 2 seconds until the daemon is ready.
This is enabled by default. No extra setup is needed beyond the normal proxy configuration.
The entire auto-start operation — including waiting for the daemon's readiness signal and detecting its bound port — is bounded by proxy.auto_start_timeout (default 30 s). If the daemon doesn't become ready within this window the browser receives a timeout error. Increase the timeout for daemons with slow initialisation:
[settings.proxy]
auto_start_timeout = "60s"Viewing Proxy URLs
Proxy URLs are shown in CLI output when the proxy is enabled and the daemon has a registered slug:
$ pitchfork start api
Daemon 'myproject/api' started on port(s): 3000
→ Proxy: https://api.localhost
$ pitchfork list
Name PID Status Proxy URL
api 12345 running https://api.localhost
$ pitchfork status api
Name: myproject/api
PID: 12345
Status: running
Port: 3000 (active)
Proxy: https://api.localhost