railway-port-tunnel
v1.2.0
Published
A small ngrok-style tunnel with a deployable relay server and local CLI.
Maintainers
Readme
railway-port-tunnel
railway-port-tunnel is a small ngrok-style tunnel made of two parts:
- A relay server you deploy to Railway or any Heroku-style Node host
- A CLI you run locally to expose
localhost:<port>through the relay
In this repo:
- The Railway-facing proxy service is server/index.js
- The local CLI is bin/railway-tunnel.js
- Railway deployment config is railway.json
The relay gives each client a URL in the form:
https://your-relay-host/t/<tunnel-id>If you configure a wildcard custom domain, the relay can also generate host-based URLs:
https://<tunnel-id>.tunnels.example.comRequests to that URL are proxied through the relay to the local machine running the CLI.
How it works
- The CLI opens a WebSocket connection to the relay.
- The relay assigns or accepts a tunnel ID and prints a public URL.
- Incoming HTTP requests to
/t/<tunnel-id>or the matching tunnel subdomain are serialized over WebSocket. - The CLI forwards each request to your local server and sends the response back.
This implementation supports multiple simultaneous tunnels by running the CLI multiple times, whether from the same machine or from different machines.
This implementation is intentionally simple:
- HTTP request and response bodies are buffered in memory
- Railway's free
*.up.railway.appdomain uses path-based routing - Host-based subdomain routing is available when you configure a wildcard custom domain
- It is designed for development and demos, not as a hardened production tunnel
Local usage
Install dependencies:
npm installExpose a local port through the live Railway relay:
npx railway-port-tunnel --pass 31415 --port 3500Request a specific subdomain:
npx railway-port-tunnel --pass 31415 --port 3000 --subdomain adminIf admin is available, the relay returns https://admin.request-tunnel.online/.
If it is already taken, the CLI exits with a clear error instead of silently choosing another URL.
Open another tunnel from the same machine in a second terminal:
npx railway-port-tunnel --pass 31415 --port 3000
npx railway-port-tunnel --pass 31415 --port 3500Each command prints one unique public URL and keeps that single tunnel alive.
The CLI now defaults to the deployed relay:
https://relay-production-55c2.up.railway.appOverride it with --server or the RAILWAY_TUNNEL_SERVER_URL environment variable when needed.
Set the shared secret with --pass or RAILWAY_TUNNEL_PASSWORD.
Start the relay server:
npm startOr start the relay plus demo page together:
npm run demo:stackStart the demo page on localhost:3500:
npm run demo:pageStart the tunnel CLI against the local relay:
npm run tunnel -- --server http://127.0.0.1:8080 --pass 31415 --port 3500The command prints a URL like:
http://127.0.0.1:8080/t/abc123xyz789Open that URL in a browser or curl it, and you should see the demo page served from your local machine.
End-to-end verification
Run the automated local test:
npm run test:e2eThis starts the relay, demo pages, and separate CLI processes, then verifies that distinct tunnel URLs proxy correctly in both path and host modes.
CLI usage
railway-tunnel --server https://your-relay-host --port 3500Options:
--port,-p: Local port to expose--server,-s: Relay base URL--subdomain,-n: Request a specific subdomain name--id,-i: Legacy alias for--subdomain--pass,-P: Shared tunnel password used during registration--host,-H: Local host to forward to, default127.0.0.1--help,-h: Show usage
Deploying the relay to Railway
This repo now includes railway.json, which sets:
- Start command:
npm start - Healthcheck:
/health
Deployment steps:
- Push this repo to GitHub.
- In Railway, create a new service from that repo.
- Deploy it.
- In Railway service settings, generate a public domain.
- Set
PUBLIC_BASE_URLto that generated domain, for examplehttps://your-app.up.railway.app. - Set
TUNNEL_PASSWORDto your shared secret, for example31415. - Redeploy so the tunnel URLs use the final public base URL and enforce registration auth.
For host-based subdomain URLs on Railway:
- Add a wildcard custom domain such as
*.tunnels.example.comto the relay service. - Set
PUBLIC_WILDCARD_DOMAIN=tunnels.example.com. - Redeploy.
Without a wildcard custom domain, the relay still supports multiple simultaneous tunnels using distinct path URLs on the single Railway service domain. With the wildcard custom domain configured, each separate tunnel command gets its own subdomain.
The server also honors the standard platform PORT environment variable, so it works on Railway and Heroku-style hosts.
Example local env values are in .env.example.
Publishing the CLI to npm
Before publishing, confirm the package name in package.json is available or rename it.
Then publish:
npm publish --access publicUsers can then run:
npx railway-port-tunnel --server https://your-relay-host --port 3500Security and limitations
- Registration authentication is a single shared secret and is not user-specific.
- Public tunneled traffic is not separately authenticated once a tunnel is registered.
- Tunnel IDs are guessable unless you supply long custom IDs.
- Large uploads/downloads are buffered rather than streamed.
- WebSocket disconnects trigger reconnect attempts from the CLI, but in-flight requests fail.
For a next iteration, add authentication, wildcard-subdomain routing, TLS termination strategy, streaming, and request logging.
