Dynamic port-to-subdomain forwarder for Caddy. Automatically detects local listening ports and creates reverse proxy routes to make them accessible via {port}.example.com on a Tailscale network. Currently the setup is hardcoded for my domain, but if you are interested, you may send PRs to make things configurable.
- Go 1.26+
- Caddy running with:
- Wildcard server configured for or your subdomain;
- TLS for your subdomain.
- Caddy admin API accessible at
http://localhost:2019 - Port forwarding, VPN, Tailscale. Make sure the subdomain is private and accessible.
Example caddy configuration:
*.subdomain.domain.com {
tls {
dns cloudflare cfut_TOKEN
resolvers 1.1.1.1
}
}make build# Start the daemon (runs in background)
make run
# Start in foreground (for debugging)
make run-fg
# Stop the daemon
make stopThis strategy is used by VSCode's port tunneling.
-
Port Detection: Reads
/proc/net/tcpand/proc/net/tcp6to find listening ports on127.0.0.1and::1, then resolves inodes to PIDs by walking/proc/*/fd/* -
Caddy Integration: Uses Caddy's admin API to discover the server handling
.local.tomascco.devsubdomains, then dynamically adds/removes reverse proxy routes -
Debouncing: Ports must be stable for 2 consecutive poll intervals (6 seconds) before being added or removed to avoid flapping
Ports below 1024 and the following are excluded: 80, 443, 2019, 3000, 8080
GET http://localhost:9090/ports— List active port forwardsGET http://localhost:9090/health— Health check
cadport/
├── main.go — Daemon lifecycle, command handling
├── detector/ports.go — Port detection via /proc/net/tcp
├── caddy/client.go — Caddy admin API client
└── server/debug.go — Debug HTTP server