DEV Community

Cover image for I got tired of localhost:3000, so I built a local dev proxy with custom domains and trusted HTTPS
Martin Voldřich
Martin Voldřich

Posted on

I got tired of localhost:3000, so I built a local dev proxy with custom domains and trusted HTTPS

Every web developer knows the routine. You start your frontend on port 3000,
your API on 3001, maybe an admin dashboard on 8080. Open a browser tab, stare
at it... which port was which again?

It gets worse when you juggle multiple projects. Two apps both want port 3000.
You're switching between client projects and can't remember if the e-commerce
site was :3000 or :3001 today. And your bookmarks bar is full of
localhost:something.

I decided to fix this for myself and ended up building
Roxy — a local development proxy that
replaces localhost ports with custom .roxy domains and browser-trusted HTTPS.

What it looks like

Instead of http://localhost:3000, you get https://myapp.roxy:

roxy register shop.roxy --route "/=3000" --route "/api=3001"
roxy register blog.roxy --route "/=4000"
sudo roxy start
Enter fullscreen mode Exit fullscreen mode

That's it. Open https://shop.roxy in your browser — green lock, no
certificate warnings, no config files. https://blog.roxy is a completely
separate project running alongside it.

Roxy demo

How it works under the hood

Roxy is a single binary that bundles three things:

  • A DNS server — resolves all .roxy domains to 127.0.0.1. No need to edit /etc/hosts or install dnsmasq.
  • A certificate authority — generates trusted HTTPS certificates on the fly. The root CA gets installed into your system trust store during roxy install, so browsers trust everything automatically.
  • A reverse proxy — routes incoming requests to your local services based on the domain and path.

One-time setup takes about 10 seconds:

# Install (macOS via Homebrew, or build from source on Linux)
brew tap rbas/roxy && brew install roxy

# One-time setup — creates Root CA, configures DNS
sudo roxy install
Enter fullscreen mode Exit fullscreen mode

After that, you just register domains and start the proxy.

Features I use every day

Path-based routing — run multiple services behind one domain:

sudo roxy register myapp.roxy \
  --route "/=3000" \
  --route "/api=3001" \
  --route "/admin=8080"

# https://myapp.roxy       → frontend
# https://myapp.roxy/api   → API
# https://myapp.roxy/admin → admin panel
Enter fullscreen mode Exit fullscreen mode

Wildcard subdomains — great for multi-tenant SaaS development:

sudo roxy register myapp.roxy --wildcard --route "/=3000"

# https://myapp.roxy            → main app
# https://acme.myapp.roxy       → tenant "acme"
# https://globex.myapp.roxy     → tenant "globex"
Enter fullscreen mode Exit fullscreen mode

Static file serving — point a route at a directory and get a built-in file
browser:

sudo roxy register docs.roxy --route "/=./build"
Enter fullscreen mode Exit fullscreen mode

Real-time traffic logs — see every request flowing through:

roxy logs -f
Enter fullscreen mode Exit fullscreen mode

The tech

I wrote Roxy in Rust. The whole thing is about 5k lines and compiles to a
single binary with zero runtime dependencies. The main building blocks:

  • axum for the HTTP server and reverse proxy
  • rustls for TLS (no OpenSSL dependency)
  • rcgen for certificate generation

It runs on macOS (Monterey+) and Linux (Ubuntu 22.04+ / Debian 12+).
The Linux support just landed in v0.5.0, which I shipped this week.

Try it

# macOS
brew tap rbas/roxy && brew install roxy

# Or build from source (macOS or Linux)
git clone https://github.com/rbas/roxy.git
cd roxy && cargo install --path .

# Then
sudo roxy install
sudo roxy register myapp.roxy --route "/=3000"
sudo roxy start
Enter fullscreen mode Exit fullscreen mode

The project is open source (MIT):
github.com/rbas/roxy

I'd love to hear how you handle local dev routing — do you just live with
localhost ports, or have you found a setup that works? Let me know in the
comments.

Top comments (2)

Collapse
 
kamalmost profile image
KamalMostafa • Edited

Good idea, thx for sharing. quick questions, How does the binary recieve DNS requests. I'm guessing the proxy edit the /etc/hosts..How do you secure the root CA private key. also, why do you need root or sudo?

Collapse
 
rbas profile image
Martin Voldřich

Great questions — these are really the core design decisions that everything else in Roxy is built on top of.

DNS — Roxy runs its own DNS server on port 53 that resolves all .roxy domains to 127.0.0.1. No /etc/hosts editing needed. On macOS it creates a resolver file at /etc/resolver/roxy, and on Linux it configures systemd-resolved — both tell the OS to route .roxy queries to Roxy's DNS server.

Root CA key — The private key is stored at /etc/roxy/ca/root.key with 0600 permissions (owner-only read/write). It never leaves your machine and is only used locally to sign certificates for your .roxy domains. It's the same approach tools like mkcert and Caddy use. That said, I'm always open to ideas here — if you've seen a better pattern for managing local CA keys, I'd love to hear it.

Why sudo — Two reasons: binding to privileged ports (53 for DNS, 80/443 for HTTP/HTTPS) and writing to system directories (/etc/roxy, /etc/resolver). The roxy install step also needs it to add the root CA to the system trust store. I've been thinking about whether it's worth supporting unprivileged ports with something like port forwarding or CAP_NET_BIND_SERVICE on Linux to reduce the sudo surface. Curious — does the sudo requirement bother you as a user, or is it expected for this kind of tool?