English | Русский
A self-hosted build of Telegram Web A where the browser only ever connects to your own domain. All Telegram traffic (MTProto WebSocket, HTTP fallback, and the t.me "websync" pings) is tunneled through a small server-side relay that talks to Telegram from your server.
browser ──wss/https──▶ tg.example.com ──▶ Caddy/nginx ──▶ Node relay ──wss/https──▶ Telegram DC
(only your origin) (TLS) (/tgproxy/*) (from your server)
The stock client connects the browser straight to *.web.telegram.org. This fork makes the browser talk only to your origin; your server (which has access to Telegram) relays the encrypted stream. The relay is a dumb byte forwarder — MTProto is encrypted end-to-end, so it never decrypts, stores, or logs payloads, auth keys, phone numbers, codes, or messages. It logs only technical events (route, token, upstream host, status).
It is not an open proxy: only /tgproxy/... routes exist and the DC token must match ^[1-5](-1)?$, so the relay can reach only Telegram's zws<dc>.web.telegram.org data centers.
- Telegram's endpoints are unreachable, but a plain HTTPS site on your domain is — and running a VPN on the device isn't an option. The client is just a normal web page on
tg.example.com. - Managed or locked-down machines — a work laptop, a kiosk, a shared computer — where you can't install a VPN client, a browser extension, or a desktop app, but a browser can open any HTTPS site.
- A single, controlled egress point: all Telegram access leaves from your server's IP, which you manage, instead of from each client device.
- Keeping client devices off third-party domains when you'd rather route everything through infrastructure you own.
Use it in accordance with the laws and network policies that apply to you.
Everything is gated behind TELEGRAM_SELF_HOSTED=1; unset, the project builds exactly like upstream.
| File | Change |
|---|---|
src/lib/gramjs/Utils.ts |
getDC() returns a DC routing token ("2", "2-1"), not a Telegram host. |
src/lib/gramjs/extensions/proxyBase.ts |
New helper resolving the proxy base URL (origin-relative by default). |
src/lib/gramjs/extensions/PromisedWebSockets.ts |
WS → wss://<origin>/tgproxy/ws/<token>. |
src/lib/gramjs/extensions/HttpStream.ts |
HTTP fallback → https://<origin>/tgproxy/http/<token>. |
src/util/websync.ts |
Disables t.me/telegram.me _websync_ script injection. |
webpack.config.ts |
connect-src 'self' CSP + new build-time env vars. |
server/, deploy/selfhosted/ |
The Node relay and deploy files (Caddy/nginx/Docker). |
cp .env.selfhosted.example .env # TELEGRAM_API_ID / TELEGRAM_API_HASH / DOMAIN / ACME_EMAIL
# point your domain's DNS at the server, then:
docker compose -f deploy/selfhosted/docker-compose.yml --env-file .env up -d --buildGet your api_id / api_hash from my.telegram.org (each operator uses their own; never commit them). Requires Node ^22.6 || ^24 for manual builds.
cp .env.selfhosted.example .env
npm install
TELEGRAM_SELF_HOSTED=1 APP_ENV=production npm run build:production # → dist/
cd server && npm install && PORT=8080 npm start # the relay
# front with deploy/selfhosted/Caddyfile or nginx.conf for TLS + /tgproxy/*URLs are origin-relative by default, so the same bundle works on any domain. Set TELEGRAM_PROXY_WS_URL / TELEGRAM_PROXY_HTTP_URL only if the relay is on a different origin.
Secure context required. The client uses
crypto.subtle, which works only onhttps://orhttp://localhost. In production behind Caddy/nginx with a real certificate this is automatic; testing from another device over plainhttp://<lan-ip>will not work.
- DevTools → Network: every request hits your origin only — no
web.telegram.org/t.me. - grep the bundle:
grep -rEi "web\.telegram\.org|/_websync_|zws[1-5]|/apiws" dist/*.js && echo LEAK || echo clean - Relay logs: only
event=ws_open token=2 host=zws2.web.telegram.org, never payloads. - The
connect-src 'self'CSP blocks any stray direct connection.
- Sessions live in the visitor's browser (IndexedDB), never on the server. Opening your URL gives a fresh login screen — a visitor cannot enter your account; they log in to their own.
- The URL is a public login portal: anyone with the link can log into their own account through your server. For a personal instance add access control (Caddy
basic_auth, Tailscale/WireGuard, or an IP allowlist). - Operator trust: whoever runs the server serves the JS. This fork exfiltrates nothing, but users logging in trust the operator — as with any self-hosted web client.
- Voice/video calls use a separate WebRTC transport and do not flow through this relay.
Fork of Telegram Web A by Ajaxy, under GNU GPL v3.0. This fork keeps the same license — see LICENSE.