# pgrok
Personal ngrok alternative. Expose local ports to the internet with automatic HTTPS, an interactive TUI dashboard, and HTTP request inspection -- all through your own VPS.
<img width="1956" height="1900" alt="image" src="https://github.com/user-attachments/assets/277ef194-a135-407a-83a4-deaad4330dee" />
## Quick Start
### Prerequisites
- A **VPS** (any provider) with ports 80 and 443 open (nothing else running on them for now)
- **Docker** + **Docker Compose** on the VPS
- A **domain name** you control
- An **SSH key** on your Mac/Linux (You can run `ssh-keygen` to generate one if you don't have one)
### 1. DNS Setup: Point your domain at the VPS
Add a wildcard A record with your DNS provider (Cloudflare, Vercel, Namecheap, etc):
| Type | Name | Value |
|------|------|-------|
| A | * | `<your-vps-ip>` |
### 2. Client Setup: Install on your Mac/Linux (run this first)
```bash
curl -fsSL https://raw.githubusercontent.com/R44VC0RP/pgrok/main/install.sh | bash -s client
```
(if you have a windows machine, and you get this running, please open a PR!)
You'll be prompted for your VPS IP, domain, and email. The installer will:
- Auto-detects your SSH key
- Builds and installs the `pgrok` command
- **Copies a server setup command to your clipboard** (with your SSH key embedded)
### 3. Server -- Set up your VPS
SSH into your VPS and **paste the command from step 2**. Or run:
```bash
curl -fsSL https://raw.githubusercontent.com/R44VC0RP/pgrok/main/install.sh | sudo bash -s server
```
The script installs Caddy, configures SSH tunneling, adds your SSH key, and starts everything.
### 4. Use it
```bash
pgrok myapp 4000
# => https://myapp.yourdomain.com -> localhost:4000
```
## Usage
```bash
# Expose a local dev server
pgrok myapp 4000
# -> https://myapp.yourdomain.com
# Expose an API
pgrok api 3000
# -> https://api.yourdomain.com
# Any subdomain works instantly
pgrok staging 8080
# -> https://staging.yourdomain.com
# Debug mode -- dumps raw tunnel logs on exit
pgrok myapp 4000 --print-logs
```
Press `Ctrl+C` to stop. The route is cleaned up automatically.
**Rebuild the binary** after pulling updates:
```bash
./setup.sh client --rebuild
```
## How it works
<img width="932" height="331" alt="image" src="https://github.com/user-attachments/assets/9d56fb43-bafe-4697-9c72-3e001a0c92aa" />
- **Caddy** on the VPS handles HTTPS with on-demand TLS -- certs are auto-provisioned per subdomain. Falls back to ZeroSSL if Let's Encrypt is rate-limited.
- **SSH reverse tunnels** carry traffic -- no extra tunnel software.
- A small **Python script** on the server dynamically configures Caddy routes when tunnels connect/disconnect.
- The **TUI client** (built with [OpenTUI](https://opentui.com)) provides a live dashboard with request inspection, connection stats, and color-coded HTTP logs.
### TUI Dashboard
The dashboard shows in real-time:
- **Session Status** -- connecting / provisioning TLS / online / error
- **Forwarding** -- your public URL and local port
- **TLS Certificate** -- provisioning status (Let's Encrypt)
- **Connection Stats** -- total requests, open connections, request rates (1m/5m), response time percentiles (p50/p90)
- **HTTP Request Log** -- scrollable, color-coded log of every request through the tunnel
Request log colors:
- Methods: GET (blue), POST (purple), PUT/PATCH (yellow), DELETE (red)
- Status: 2xx (green), 3xx (cyan), 4xx (yellow), 5xx (red)
- Duration: <100ms (green), 100-500ms (yellow), >500ms (red)
## How SSL Works
1. A request arrives for `myapp.yourdomain.com`
2. Caddy checks with `pgrok-ask`: "Should I get a cert for this domain?"
3. `pgrok-ask` verifies it's a single-level subdomain of `*.yourdomain.com`
4. Caddy uses HTTP-01 challenge to get a cert -- tries Let's Encrypt first, falls back to ZeroSSL if rate-limited
5. Cert is cached and auto-renewed
6. The TUI client triggers cert provisioning during tunnel setup, so it's ready before you open the URL
## Security
- SSH key authentication only (no passwords)
- Dedicated `pgrok` user with restricted SSH (remote forwarding only)
- Caddy admin API only on localhost (not exposed externally)
- SSH tunnels bind to localhost only (`GatewayPorts no`)
- `pgrok-ask` prevents cert abuse -- only allows single-level subdomains
## Configuration
### Client (`~/.pgrok/config`)
Generated by the installer. Edit manually if needed:
```bash
PGROK_HOST=your-vps-ip
PGROK_DOMAIN=yourdomain.com
PGROK_USER=pgrok
PGROK_SSH_KEY=~/.ssh/id_ed25519
```
### Server (`/opt/pgrok/`)
| File | Purpose |
|------|---------|
| `Caddyfile` | On-demand TLS config with LE + ZeroSSL |
| `docker-compose.yml` | Caddy container |
| Script (`/usr/local/bin/`) | Purpose |
|----------------------------|---------|
| `pgrok-tunnel` | Manages Caddy routes + provisions TLS certs |
| `pgrok-ask` | Validates cert requests (systemd service) |
## Troubleshooting
**Stuck on "connecting" in the TUI:**
- Run with `--print-logs`, press Ctrl+C, check `/tmp/pgrok-debug.log`
- Verify SSH works: `ssh pgrok@your-vps-ip echo ok`
**Stuck on "provisioning TLS...":**
- Let's Encrypt may be rate-limited (50 certs/week). ZeroSSL fallback handles this.
- Check Caddy logs: `docker compose logs caddy` in `/opt/pgrok`
**SSL certificate errors:**
- Verify `pgrok-ask` is running: `systemctl status pgrok-ask`
- Make sure ports 80 and 443 are open
- Cloudflare proxy (orange cloud) must be OFF for the wildcard record
## Development
```bash
cd client/tui
bun install
bun run index.ts myapp 4000 # dev mode
bun run build # compile binary
bun run tsc --noEmit # type-check
```
## Limitations
- Single user (personal tool, not multi-tenant)
- Mac and Linux only (no Windows)
- No automatic reconnection (restart `pgrok` if connection drops)
- Stale routes possible on abrupt disconnection (self-heal on next connect)
- HTTP request logging only (WebSocket passthrough works but isn't logged)
## CONTRIBUTING
We welcome contributions! Please open an issue or PR.
## LICENSE
MIT