Telebit Relay
Self-hosted Telebit relay and management API for running behind an external nginx wildcard HTTPS setup.
Server Setup
Create .env from .env.example:
cp .env.example .env
Set at least:
DOMAIN=t.renman.dev
TUNNEL_DOMAIN=t.renman.dev
SECRET=change-this-to-a-long-random-secret
POSTGRES_PASSWORD=change-this-postgres-password
TELEBIT_PORT=8085
MGMT_PORT=6468
Start the relay:
docker compose up -d --build
The relay listens on 127.0.0.1:8085 through Docker's published port, and management is bound to 127.0.0.1:6468.
Nginx
Nginx should terminate HTTPS. The relay container is patched to run as a plain HTTP/WebSocket backend and should not handle Let's Encrypt itself.
Use plain http:// upstreams. A standalone template is available in example-nginx.conf:
server {
listen 80;
listen [::]:80;
server_name t.example.com *.t.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name t.example.com *.t.example.com;
ssl_certificate /etc/letsencrypt/live/t.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/t.example.com/privkey.pem;
location = /ws {
proxy_pass http://127.0.0.1:8085/ws;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Authorization $http_authorization;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
location /api/ {
proxy_pass http://127.0.0.1:6468/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://127.0.0.1:8085;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Reload:
sudo nginx -t
sudo systemctl reload nginx
Client Wrapper
telebitctl manages local client keys and runs the Telebit client.
Build local binaries with Docker:
./telebitctl build
This writes:
bin/telebit
bin/signjwt
Create a new tunnel key for a subdomain:
./telebitctl bind test
This creates test.$DOMAIN and writes .telebit-clients/test.env.
Run the tunnel to a local service:
./telebitctl up test 80
That forwards:
https://test.$DOMAIN -> localhost:80
up uses a per-slug client config. If .telebit-clients/test.env already exists, it reuses that key and only updates the local port. If it does not exist, it binds test before starting the tunnel.
Because each slug has its own env file, multiple tunnels can run at the same time:
telebitctl up app 3000
telebitctl up api 8080
up restarts the client if the WebSocket connection drops. By default it waits 5 seconds and retries forever. You can tune that with:
TELEBIT_RETRY_DELAY=10 telebitctl up app 3000
TELEBIT_RETRY_MAX=3 telebitctl up app 3000
Rotate the client key:
./telebitctl auth refresh test
Release a subdomain:
./telebitctl release test
Notes
.env,.telebit-clients/,telebit-client.env, andbin/are ignored by git.SECRETin.envis the management/relay master secret. Do not use it directly as a long-lived client key.- Client keys are one-time registration secrets. Use
bindorauth refreshto create device-specific keys. - If
/wsreturns nginx502, nginx is probably still proxying tohttps://127.0.0.1:8085; it must usehttp://127.0.0.1:8085.