https://github.com/0xrelogic/tunly
Tunly is a lightweight, open-source HTTP tunnel solution for developers who want full control and instant setup. No quotas. No dashboards. No tracking. Just you, your app, and a tunnel pure and simple.
https://github.com/0xrelogic/tunly
developer-tools localhost-tunnel port-forwarding reverse-proxy rust self-hosted server tunnel-client tunnel-server websocket
Last synced: 2 months ago
JSON representation
Tunly is a lightweight, open-source HTTP tunnel solution for developers who want full control and instant setup. No quotas. No dashboards. No tracking. Just you, your app, and a tunnel pure and simple.
- Host: GitHub
- URL: https://github.com/0xrelogic/tunly
- Owner: 0xReLogic
- License: mit
- Created: 2025-07-12T05:49:02.000Z (12 months ago)
- Default Branch: main
- Last Pushed: 2026-04-09T17:41:01.000Z (2 months ago)
- Last Synced: 2026-04-09T19:23:16.996Z (2 months ago)
- Topics: developer-tools, localhost-tunnel, port-forwarding, reverse-proxy, rust, self-hosted, server, tunnel-client, tunnel-server, websocket
- Language: Rust
- Homepage:
- Size: 1.33 MB
- Stars: 9
- Watchers: 0
- Forks: 1
- Open Issues: 12
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Security: SECURITY.md
Awesome Lists containing this project
README
# Tunly
[](https://www.rust-lang.org/)
[](https://opensource.org/licenses/MIT)
[](https://github.com/0xReLogic/Tunly/releases)
[](https://github.com/0xReLogic/Tunly/releases)
[](https://github.com/0xReLogic/Tunly/actions/workflows/codeql.yml)
[](https://github.com/0xReLogic/Tunly/actions/workflows/rust-ci.yml)
[](https://github.com/0xReLogic/Tunly/actions/workflows/frontend-ci.yml)
[](https://github.com/0xReLogic/Tunly/actions/workflows/release.yml)
**Tunly** is a simple, lightweight, and open-source HTTP tunnel solution for exposing local applications to the internet.
---
## Motivation
**Tunly** is built for developers, makers, and anyone who wants:
- **Access local applications from anywhere** without hassle
- **No login, no dashboard, no limits**
- **100% open source** and self-hostable
- **Easy distribution**: simple setup with token-based authentication
---
## Key Features
- **Zero Configuration**: No login, no dashboard, and no complex registration.
- **Advanced Security**: Secure **JWT-based authentication** with IP binding and single-use protection.
- **High Performance**: Native **HTTP/2 support** with multiplexing and transparent **Zlib compression**.
- **Full Observability**: Prometheus metrics (`/metrics`), structured JSON logging, and a built-in session activity viewer (`/_log`).
- **Production Built**: Lightweight binary with persistent connection pooling and enforced security limits.
- **Self-Hostable**: Easily deploy on any VPS or Cloud (DigitalOcean, Vultr, Koyeb, etc.).
- **Privacy First**: 100% open-source with zero tracking or telemetry.
---
## When to Use Tunly?
### **Demo & Presentations**
Client wants direct access to your app? But your project is still on localhost?
**Solution**: Tunly makes your localhost accessible from anywhere in 30 seconds.
### **Client Testing**
Client needs to test new features but you haven't deployed to production yet?
**Solution**: Share tunnel URL, client can test immediately without complex setup.
### **Development & Debugging**
Remote work but need access to apps on your home computer?
**Solution**: Tunnel from home to office, access applications from anywhere.
### **Mobile Testing**
Need to test web apps on phone but they only run on laptop?
**Solution**: Tunnel laptop, access from phone via WiFi or data.
### **Quick Prototyping**
Have a new idea, want to share with friends but haven't deployed yet?
**Solution**: Tunnel localhost, share URL, friends can try immediately.
### **Private Testing**
Want to test apps on the internet without complex setup?
**Solution**: Tunly provides simple, self-hosted tunneling without hassle.
---
## How to Use
### Modes
- **Self-host**: Run your own server on a VPS or cloud platform and point the client to it.
- **Local testing**: Run both server and client locally for development.
### Self-Hosted Setup
1. **Download** `tunly-client` and `tunly-server` for your OS from [Releases](https://github.com/0xReLogic/Tunly/releases)
2. **Start the server** on your VPS or cloud:
```bash
tunly-server --port 8080
```
3. **Run the client** locally:
```bash
tunly-client --remote-host your-server.com:8080
```
4. When prompted, get a token from `http://your-server.com:8080/token` and paste it
5. Enter your local address when prompted (default: `127.0.0.1:80`)
6. The client will print your **Public URL**, e.g., `https://your-server.com/s//` — share this URL
7. View the session log: `https://your-server.com/s//_log`
> Notes:
> - Long flags use kebab-case (e.g., `--remote-host`, `--token-url`, `--allow-token-query`).
> - Default auth uses header `Authorization: Bearer `. Query `?token=...` works only if server enables `--allow-token-query`.
> - For self-host without TLS, pass `--use-wss=false` so the client uses `ws://` (the flag accepts an explicit boolean, e.g., `--use-wss=false`).
### Quick start (from source, via Cargo)
If building from source:
- **Self-host** — run your own server, then point client to it.
1) Start server (ephemeral token mode):
```
cargo run --bin tunly-server -- --bind 0.0.0.0:9000
```
2a) Start client (interactive, custom server):
```
cargo run --bin tunly-client -- --remote-host :9000 --use-wss=false --local 127.0.0.1:8080
```
2b) Start client (auto-fetch token):
```
cargo run --bin tunly-client -- --remote-host :9000 \
--use-wss=false \
--local 127.0.0.1:8080 \
--token-url http://:9000/token
```
3) Open the Public URL printed by the client, e.g.:
```
http://:9000/s//
```
4) Check recent paths accessed by visitors for that session:
```
http://:9000/s//_log
```
#### Local offline test (no TLS)
For a quick local test without internet:
1) Start server with a fixed token:
```
cargo run --bin tunly-server -- --bind 127.0.0.1:9000 --token devtoken
```
2) Start client (interactive) and connect over ws:
```
cargo run --bin tunly-client -- --remote-host 127.0.0.1:9000 --use-wss=false
```
When prompted, enter `devtoken`, then your local app address (e.g., `127.0.0.1:8080`).
### Loginless / Ephemeral Token Mode (no dashboard, no signup)
If you don't want to manage a static token, run the server without `--token` and without env `TUNLY_TOKEN`. The server will issue one-time tokens bound to the requester's IP via `/token`.
- **Start server (ephemeral mode)**
```
tunly-server.exe --port 9000
```
- **Client auto-fetch token (advanced)**
```
tunly-client.exe --remote-host :9000 --token-url http://:9000/token
```
The client fetches a token from `/token` (JSON or plain text) and connects via WebSocket using that token.
Notes:
- Tokens are one-time use, may be bound to the requester IP, and expire in ~5 minutes.
- Default auth is via header `Authorization: Bearer `; `?token=` query is disabled unless `--allow-token-query` is set on the server.
- If you prefer a fixed token, set `--token ` or env `TUNLY_TOKEN` on the server and keep using `config.txt` or env on the client.
### Fixed vs Ephemeral Tokens
- **Fixed Token**
- Server: run with `--token ` or env `TUNLY_TOKEN`.
- Client: paste token when prompted, or set it via `config.txt`/`TUNLY_TOKEN`.
- Best for interactive UX testing and simple setups.
- **Ephemeral Token**
- Server: run without `--token` (issues one-time tokens via `/token`, tied to `session`+IP, TTL ~5 minutes).
- Client: use `--token-url http://:/token` so the token matches the current `sid` automatically.
- Manual prompt is not compatible with Ephemeral mode (will be rejected as invalid).
### Server Hosting Options
- **Cheap VPS**: DigitalOcean, Vultr, Linode ($5/month)
- **Free cloud**: Oracle Cloud Free Tier, Google Cloud Free Tier
- **Platform-as-a-Service**: Render, Railway, Koyeb (easy deployment)
---
## Environment & Deploy
You can configure Tunly using environment variables. See the `.env.example` files in the root, `backend/`, and `frontend/` directories for templates.
- **Server env**:
- `PORT` (from platform, e.g., Render, Koyeb) — server listens on this port automatically.
- `TUNLY_TOKEN` — optional; if set, server uses fixed-token mode. If not set and `--token` is not provided, server uses ephemeral mode with `/token` issuance.
- `TUNLY_INTERNAL_KEY` — optional; if set, restricts `/token` access to requests providing this key in the `X-Internal-Key` header (prevents direct `curl` requests to your backend).
- **Client config**:
- `config.txt` with `token: ` (tolerant to `token=`/`token:`/`tokenn`).
- Or env `TUNLY_TOKEN`.
- Or runtime fetch via `--token-url http://:/token` (ephemeral mode).
- **Frontend env**:
- `BACKEND_BASE_URL` — base URL of your Tunly backend (e.g., `https://.koyeb.app` or your custom domain). Used by the Next.js proxy route `app/api/token/route.ts` to call `/token`.
- `TUNLY_INTERNAL_KEY` — must match the server's key to allow the frontend to fetch tokens securely via the Next.js API route.
- **Deploy on Koyeb**:
- Source: Docker → Dockerfile path: `backend/Dockerfile`
- Health check: `GET /healthz`
- Environment:
- `TUNLY_TOKEN` (optional): set for Fixed mode; leave empty for Ephemeral mode (`/token` enabled)
- `PORT`: injected automatically by Koyeb (no need to set)
- Optional: add a custom domain; Koyeb will provision TLS automatically
---
## Security & Limits
- `/token` rate limit: 10 requests per 60 seconds per IP
- Ephemeral token TTL: ~5 minutes; single use; bound to requester's IP and session id
- Proxy request body limit: 2 MB
- Session idle TTL: ~10 minutes (inactive sessions are garbage-collected)
---
## Logs & Observability
- **Server logs** each proxied request:
```
PROXY GET / -> 200 in 16ms (sid=abc123)
```
- **Client logs** each local request it performs:
```
LOCAL GET /api -> 200 in 8ms
```
- **Session log page** lists the last ~50 requests for a session:
- URL: `http:///s//_log`
- Shows: Method, URI, Status, Duration (ms)
- Includes quick links to `/, /api, /blog` for quick checks
## API Endpoints
- `GET /healthz` — health check
- `GET /token` — issue ephemeral token (available only in Ephemeral mode)
- `GET /ws?sid=` — WebSocket entrypoint (use `Authorization: Bearer ` header)
- `GET /s/:sid/_log` — recent paths accessed for the session
- `ANY /s/:sid/<...>` — proxied traffic routed to the connected client
## Troubleshooting
- **“Token is invalid or has expired.”**
- Cause: Server is in Ephemeral mode but client used manual token prompt (token doesn’t match `sid`).
- Fix: Use `--token-url http://:/token`, or run server with a fixed token (`--token `) and then use manual prompt.
- **Cannot connect over wss on localhost/self-host**
- Cause: No TLS certificate for your self-hosted server.
- Fix: Add `--use-wss=false` to use plain `ws://` during local testing.
## Security
- Token is the "password" for your tunnel.
- Don't share tokens with untrusted people.
- Change tokens regularly for extra security.
---
## License
MIT License — free to use for commercial and non-commercial purposes.