- TypeScript 90.1%
- Shell 9.3%
- Dockerfile 0.6%
| .github | ||
| art | ||
| src | ||
| .bun-version | ||
| .dockerignore | ||
| .gitignore | ||
| bun.lock | ||
| Dockerfile | ||
| index.ts | ||
| install.sh | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
| renovate.json | ||
| Taskfile.yml | ||
| tsconfig.json | ||
w
A self-hosted alternative to ngrok to tunnel local HTTP to the web.
Comparison
| Feature | w-share | ngrok | Cloudflare Tunnel | localhost.run | serveo.net |
|---|---|---|---|---|---|
| Self-hosted | ✅ Yes | ❌ No | ❌ No | ❌ No | ❌ No |
| No account required | ✅ Yes | ❌ No | ❌ No | ✅ Yes | ✅ Yes |
| Own domain control | ✅ Yes | ⚠️ Optional | ✅ Yes | ⚠️ Optional | ⚠️ Optional |
| Custom subdomains | ✅ | ⚠️ Plan-based | ✅ | ⚠️ Limited | ⚠️ Limited |
| Authentication | ✅* | ⚠️ Plan/config | ⚠️ Access config | ❌ | ❌ |
| Session duration | ✅ Unlimited | ✅ No timeout | ✅ Persistent | ⚠️ Not stated | ⚠️ Not stated |
| QR sharing | ✅ | ❌ | ❌ | ❌ | ❌ |
| Server control | ✅ Full | ⚠️ Limited | ⚠️ Cloudflare-managed | ❌ | ❌ |
w-sharesupports Basic Auth per tunnel.
w-share is best when you want a small tunnel server under your own control. Hosted tools are usually easier to start with, but depend on third-party infrastructure and account limits.
Table of contents
Client
Install
The release installer downloads the matching macOS or Linux binary for your machine and verifies it with checksums-sha256.txt. It installs to ~/.local/bin/w-share by default.
# macOS and Linux: installs as ~/.local/bin/w-share
curl -fsSL https://raw.githubusercontent.com/eznix86/w-share/main/install.sh | sh
Pin a specific release by passing the tag explicitly.
curl -fsSL https://raw.githubusercontent.com/eznix86/w-share/main/install.sh | sh -s -- v1.0.1-alpha.0
Override the install directory with W_INSTALL_DIR if you want a different target path.
curl -fsSL https://raw.githubusercontent.com/eznix86/w-share/main/install.sh | W_INSTALL_DIR=/usr/local/bin sh -s -- v1.0.1-alpha.0
Override the installed command name with W_BINARY_NAME if needed.
curl -fsSL https://raw.githubusercontent.com/eznix86/w-share/main/install.sh | W_BINARY_NAME=my-w-share sh
If ~/.local/bin is not already in your PATH, add it in your shell profile:
export PATH="$HOME/.local/bin:$PATH"
Expose a local target
w-share http :8000
w-share http https://awesome-local-website.localhost
w-share share :8000 --name docs
w-share share :8000 --qr
w-share share :8000 --auth
The client prompts for the server URL and shared token on first run and stores them in ~/.config/w-share/config.json.
Use --qr to print a terminal QR code for the assigned public URL.
Use --name to request a custom public subdomain like docs.share.domain.tld.
Use --auth to protect the public URL with Basic Auth. Pass username:password inline or omit the value to be prompted.
The prompt form is recommended so credentials do not end up in your shell history.
To update the saved client configuration later:
w-share config
Print the installed version for scripts with:
w-share version --json
w-share version --text
Update the installed CLI with the latest release:
w-share update
Install a specific release with:
w-share update v1.0.4
Uninstall the CLI, installed completions, and legacy w binary if it belongs to this project:
w-share uninstall
Also remove saved client configuration with:
w-share uninstall --purge
Shell completions
The installer adds completions automatically for detected bash, zsh, and fish setups. Disable this with W_INSTALL_COMPLETIONS=0 if you prefer to manage completions yourself.
Generate completions manually with:
w-share completion bash
w-share completion zsh
w-share completion fish
Install them with:
# bash
mkdir -p ~/.local/share/bash-completion/completions
w-share completion bash > ~/.local/share/bash-completion/completions/w-share
# zsh
mkdir -p ~/.zsh/completions
w-share completion zsh > ~/.zsh/completions/_w-share
# fish
mkdir -p ~/.config/fish/completions
w-share completion fish > ~/.config/fish/completions/w-share.fish
Server
Run directly
W_SHARE_TOKEN=dev-token w-share serve --domain share.domain.tld --port 8080
Generate a token with:
openssl rand -hex 24
Docker Compose
services:
w-share:
image: ghcr.io/eznix86/w:1.0.0-alpha.1
restart: unless-stopped
command:
- serve
- --domain
- share.domain.tld
- --port
- "8080"
environment:
W_SHARE_TOKEN: your-shared-token
ports:
- "8080:8080"
Replace:
share.domain.tldwith your public tunnel domainyour-shared-tokenwith the token used by your clients
Kubernetes
Use ghcr.io/eznix86/w:<tag> in your Deployment and pass the server args to the container:
containers:
- name: w-share
image: ghcr.io/eznix86/w:1.0.0-alpha.1
imagePullPolicy: IfNotPresent
args:
- serve
- --domain
- share.domain.tld
- --port
- "8080"
env:
- name: W_SHARE_TOKEN
valueFrom:
secretKeyRef:
name: w-share-auth
key: token
Your Ingress should route both the root domain and wildcard subdomains to the same service:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: w-share
spec:
ingressClassName: nginx
rules:
- host: share.domain.tld
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: w-share
port:
number: 80
- host: '*.share.domain.tld'
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: w-share
port:
number: 80
Replace:
share.domain.tldwith your public tunnel domainw-sharewith your Service name if different
If you use cert-manager, request a certificate for both the root domain and wildcard subdomain:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: w-share-tls
spec:
secretName: w-share-tls
issuerRef:
name: your-cluster-issuer
kind: ClusterIssuer
dnsNames:
- share.domain.tld
- '*.share.domain.tld'
