paw-proxy

module
v1.12.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 14, 2026 License: MIT

README

paw-proxy

Stop fighting localhost. Named HTTPS domains for every local dev server.

Why paw-proxy?

Three Next.js projects. All want port 3000. The second one silently bumps to 3001. Your OAuth callback is hardcoded to localhost:3000. Your test fixtures expect it. You've been debugging the wrong app for 20 minutes.

Using git worktrees? Same project, two branches, both on localhost — which tab is which? You just spent 10 minutes testing code that hasn't changed because you're hitting the wrong instance.

The problem isn't ports. It's identity. localhost:3000 doesn't tell you what you're running. A named domain does. And once you have names, HTTPS comes free — real trusted certificates, no browser warnings, OAuth callbacks that just work.

# Before: port conflicts and confusion
npm run dev  # → localhost:3000... or 3001? which project is this?

# After: named domains, just works
up npm run dev  # → https://myapp.test ✓

Features

  • Zero config - Just run up bun dev and get HTTPS
  • Auto SSL - Generates trusted certificates on-the-fly
  • WebSocket support - Hot reload works out of the box
  • Smart naming - Uses package.json name or directory name
  • Docker Compose - Auto-discovers services and creates service.project.test routes
  • Conflict resolution - Automatic fallback when a domain is already in use (great for git worktrees)
  • Live dashboard - Real-time request feed and route status at https://_paw.test

Installation

brew install alexcatdad/tap/paw-proxy

# Run setup (creates CA, configures DNS, installs daemon)
sudo paw-proxy setup

Usage

# Wrap any dev server command
up bun dev
up npm run dev
up yarn dev

# Custom domain name
up -n myapp npm start

# Check status
paw-proxy status

Your app is now available at https://<name>.test

Docker Compose

Wrap docker compose up to get HTTPS domains for every service with published ports:

~/projects/myapp$ up docker compose up
Mapping https://frontend.myapp.test -> localhost:3000...
Mapping https://api.myapp.test -> localhost:8080...
2 services live:
   https://frontend.myapp.test
   https://api.myapp.test
------------------------------------------------

Services without published ports (like databases) are skipped. The project name comes from your compose config — override it with -n:

# Custom project name
up -n shop docker compose up
# → https://frontend.shop.test, https://api.shop.test

# With compose flags (profiles, custom files)
up docker compose --profile frontend up
up docker compose -f compose.prod.yml up
Dashboard

Visit https://_paw.test to see a live dashboard with:

  • Active routes and their uptime, request counts, and average latency
  • Real-time request feed via Server-Sent Events
  • Filter requests by route (click any route row)
Git Worktrees

Running multiple branches of the same project? paw-proxy handles it automatically. When two instances of up register the same name (e.g., from a shared package.json), the second instance falls back to its directory name:

# Main checkout: ~/myapp/
up bun dev
# → https://myapp.test

# Worktree: ~/myapp-feat-auth/
up bun dev
# ⚠️  myapp.test already in use from ~/myapp
#    Using myapp-feat-auth.test instead
# → https://myapp-feat-auth.test

You can also set an explicit name with -n:

up -n staging bun dev
# → https://staging.test

How It Works

  1. DNS - A local DNS server resolves *.test to 127.0.0.1
  2. SSL - A trusted CA generates certificates for each domain on-the-fly
  3. Proxy - HTTPS requests are proxied to your dev server's local port
  4. Auto-port - up finds a free port and sets PORT environment variable

Architecture

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   Browser   │────▶│  paw-proxy   │────▶│  Dev Server │
│             │     │  (port 443)  │     │  (dynamic)  │
└─────────────┘     └──────────────┘     └─────────────┘
                           │
                    ┌──────┴──────┐
                    │  DNS Server │
                    │  (port 9353)│
                    └─────────────┘

Commands

paw-proxy
Command Description
setup Configure DNS, CA, and install daemon (requires sudo)
uninstall Remove all paw-proxy components
status Show daemon status and registered routes
run Run daemon in foreground (for launchd)
version Show version
up
up [-n name] [--restart] <command> [args...]

Options:
  -n name    Custom domain name (default: package.json name or directory)
  --restart  Auto-restart on crash (non-zero exit, single-app mode only)

Docker Compose mode:
  up docker compose up           Auto-discover services, register routes
  up -n shop docker compose up   Override project name portion
  up docker compose --profile frontend up   Compose flags supported

Environment variables set for your command:
  PORT                 - The port your server should listen on (single-app mode)
  APP_DOMAIN           - e.g., myapp.test (single-app mode)
  APP_URL              - e.g., https://myapp.test (single-app mode)
  HTTPS                - "true" (single-app mode)
  NODE_EXTRA_CA_CERTS  - Path to CA cert (for Node.js HTTPS requests)

Troubleshooting

Firefox doesn't trust the certificate

Firefox uses its own certificate store. Install NSS:

brew install nss
paw-proxy setup  # Re-run to update Firefox
"Daemon not running" error
# Check status
paw-proxy status

# Re-run setup
sudo paw-proxy setup
Port 80/443 already in use

Stop any other web servers (nginx, Apache, etc.) before running setup.

Uninstall

paw-proxy uninstall

Development

# Build
go build -o paw-proxy ./cmd/paw-proxy
go build -o up ./cmd/up

# Test
go test -v ./...

# Integration tests (requires setup)
sudo ./paw-proxy setup
./integration-tests.sh

Inspiration & Prior Art

paw-proxy stands on the shoulders of giants. This project wouldn't exist without:

  • mkcert - The gold standard for local CA generation. We learned a lot from how it handles certificate trust.
  • puma-dev - The original .test domain proxy for macOS. Our architecture mirrors many of its ideas.
  • pow - The OG that started it all. RIP.
  • hotel - Cross-platform proxy with a nice UI. Inspired our zero-config approach.
  • caddy - Automatic HTTPS done right. We borrowed their "just works" philosophy.

We didn't reinvent the wheel — we just modernized it for modern dev workflows where every project needs named HTTPS domains.

License

MIT

Directories

Path Synopsis
cmd
gen-man command
gen-man generates roff-formatted man pages from the structured help data in internal/help.
gen-man generates roff-formatted man pages from the structured help data in internal/help.
paw-proxy command
cmd/paw-proxy/main.go
cmd/paw-proxy/main.go
up command
cmd/up/main.go
cmd/up/main.go
internal
api
internal/api/routes.go
internal/api/routes.go
daemon
internal/daemon/daemon.go
internal/daemon/daemon.go
dns
internal/dns/server.go
internal/dns/server.go
errorpage
internal/errorpage/errorpage.go
internal/errorpage/errorpage.go
help
Package help provides structured help data and rendering for paw-proxy CLIs.
Package help provides structured help data and rendering for paw-proxy CLIs.
launchd
internal/launchd/launchd_other.go
internal/launchd/launchd_other.go
paths
Package paths provides platform-aware path resolution for paw-proxy.
Package paths provides platform-aware path resolution for paw-proxy.
proxy
internal/proxy/proxy.go
internal/proxy/proxy.go
ssl
internal/ssl/ca.go
internal/ssl/ca.go

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL