MTProto Proxy Health Monitor
Two-stage health checks for Telegram MTProto proxies with a real-time web dashboard, Prometheus metrics, and monitoring integrations.
TDMeter monitors your Telegram MTProto proxy servers by running two-stage health checks and reporting the results through a beautiful dark-themed web dashboard, a JSON API, per-proxy health endpoints, and Prometheus metrics.
No Telegram account or authentication is required. TDLib's proxy testing works in an unauthenticated state.
- π¬ Two-stage health checks β TCP connectivity test + TDLib MTProto protocol verification
- π’π‘π΄ Three-state status model β Online, Degraded, and Offline for precise diagnostics
- π₯οΈ Real-time web dashboard β Dark theme, auto-refresh, status filtering (Alpine.js + custom CSS)
- π Prometheus metrics β Five gauges covering proxy status, latency, and check duration
- π©Ί Per-proxy health endpoints β
/health/{name}returns 200/503 for Uptime Kuma integration - π JSON API β
/api/statusfor custom integrations - π Optional Basic Auth β Protects web routes while keeping
/metricsopen for Prometheus - βοΈ YAML + env var config β File-based configuration with environment variable overrides
- π³ Docker ready β Multi-stage build, single binary, ~30MB runtime image
- π¦ Single binary β All templates and the logo are embedded via
go:embed
TDMeter performs a two-stage check for every proxy on each interval:
ββββββββββββββββ βββββββββββββββββββββ ββββββββββββββββββββ
β Stage 1 β OK β Stage 2 β OK β Status: β
β TCP Connect βββββββΊβ TDLib pingProxy βββββββΊβ π’ Online β
ββββββββ¬ββββββββ ββββββββββ¬βββββββββββ ββββββββββββββββββββ
β β
β FAIL β FAIL
βΌ βΌ
ββββββββββββββββββββ ββββββββββββββββββββ
β Status: β β Status: β
β π΄ Offline β β π‘ Degraded β
ββββββββββββββββββββ ββββββββββββββββββββ
| Status | TCP | TDLib | Meaning |
|---|---|---|---|
| π’ Online | β | β | Proxy is fully functional |
| π‘ Degraded | β | β | Server reachable but MTProto protocol fails |
| π΄ Offline | β | β | Server unreachable, TDLib check is skipped |
# 1. Create your config file
cp config.example.yaml config.yaml
# Edit config.yaml with your Telegram API credentials and proxy list
# 2. Build and run
docker build -t tdmeter .
docker run -d \
-v $(pwd)/config.yaml:/etc/tdmeter/config.yaml:ro \
-p 2112:2112 \
--name tdmeter \
tdmeterOpen http://localhost:2112 for the dashboard, or check metrics at http://localhost:2112/metrics.
Create a docker-compose.yml:
services:
tdmeter:
build: .
volumes:
- ./config.yaml:/etc/tdmeter/config.yaml:ro
ports:
- "2112:2112"
restart: unless-stoppeddocker compose up -dπ‘ Note: TDLib compilation requires 4GB+ RAM. The Docker build uses a three-stage approach:
- Build TDLib from source (Alpine + CMake)
- Build Go binary with CGO and static TDLib linking
- Minimal Alpine runtime image (~30MB)
- Go 1.24+
- TDLib (built from source, pinned to commit
22d49d5for go-tdlib v0.7.6 compatibility) - Telegram API credentials β
api_idandapi_hashfrom my.telegram.org
Install TDLib first. See the TDLib build instructions.
# Clone and build
git clone https://github.com/belaytzev/tdmeter.git
cd tdmeter
# Build with TDLib support (CGO required)
CGO_ENABLED=1 go build -tags=tdlib -o tdmeter .
# Copy and edit config
cp config.example.yaml config.yaml
# Run
./tdmeter --config config.yaml# Run unit tests (no TDLib required)
go test ./...
# TDLib integration tests require the tdlib build tag and a working TDLib installation
CGO_ENABLED=1 go test -tags=tdlib ./...TDMeter uses a YAML config file with optional environment variable overrides.
Copy config.example.yaml and edit it:
tdlib:
api_id: 12345 # From https://my.telegram.org
api_hash: "your_api_hash_here"
db_path: "/tmp/tdmeter-tdlib/"
proxies:
- name: "proxy-eu-1"
server: "proxy1.example.com"
port: 443
secret: "ee0123456789abcdef0123456789abcdef"
- name: "proxy-us-1"
server: "proxy2.example.com"
port: 8443
secret: "dd0123456789abcdef0123456789abcdef"
metrics:
listen: ":2112"
web:
auth:
username: "" # Leave empty to disable auth
password: ""
check_interval: 60s
tcp_timeout: 5s
tdlib_timeout: 10s
concurrency: 5| Field | Default | Description |
|---|---|---|
tdlib.api_id |
(required) | Telegram API ID |
tdlib.api_hash |
(required) | Telegram API Hash |
tdlib.db_path |
/tmp/tdmeter-tdlib/ |
TDLib database directory |
proxies |
(required, min 1) | List of MTProto proxies to monitor |
proxies[].name |
(required) | Display name for the proxy |
proxies[].server |
(required) | Proxy server hostname or IP |
proxies[].port |
(required) | Proxy port (1-65535) |
proxies[].secret |
(required) | Hex-encoded MTProto secret |
metrics.listen |
:2112 |
Address for HTTP server (dashboard + metrics) |
web.auth.username |
(empty) | Basic auth username (set both or neither) |
web.auth.password |
(empty) | Basic auth password (set both or neither) |
check_interval |
60s |
How often to run health checks |
tcp_timeout |
5s |
TCP connection timeout |
tdlib_timeout |
10s |
TDLib proxy test timeout |
concurrency |
5 |
Maximum concurrent proxy checks |
Secrets are hex-encoded. The prefix byte determines the mode:
| Prefix | Mode | Description |
|---|---|---|
ee |
Fake-TLS | Most common. Wraps MTProto in TLS to avoid detection |
dd |
Padded intermediate | Adds padding to obfuscate traffic patterns |
| (none) | Simple intermediate | Basic MTProto obfuscation |
Environment variables take precedence over YAML values:
| Variable | Overrides | Example |
|---|---|---|
TDMETER_API_ID |
tdlib.api_id |
TDMETER_API_ID=12345 |
TDMETER_API_HASH |
tdlib.api_hash |
TDMETER_API_HASH=abc123... |
TDMETER_AUTH_USERNAME |
web.auth.username |
TDMETER_AUTH_USERNAME=admin |
TDMETER_AUTH_PASSWORD |
web.auth.password |
TDMETER_AUTH_PASSWORD=secret |
TDMeter ships with a built-in dark-themed web dashboard at the root URL (/).
Dashboard features:
- π Summary stats bar β total, online, degraded, offline counts at a glance
- π Status filter buttons β quickly filter proxies by status
- π Auto-refresh β polls the API on each check interval (toggleable)
- β‘ Latency display β shows RTT in milliseconds for online proxies
- π Health endpoint links β hover any proxy card to copy its health URL
- π± Responsive design β works on desktop and mobile
- π¨ Custom logo support β replace
web/logo.pngand rebuild
TDMeter exposes per-proxy health endpoints designed for Uptime Kuma:
- In Uptime Kuma, create a new monitor of type HTTP(s)
- Set the URL to
http://your-tdmeter:2112/health/{proxy-name} - Set expected status code to 200
- The endpoint returns 200 when the proxy is online and 503 when degraded or offline
Example:
http://localhost:2112/health/proxy-eu-1 β 200 {"status":"online","latency_ms":142.5}
http://localhost:2112/health/proxy-us-1 β 503 {"status":"offline"}
π‘ Tip: If you enabled Basic Auth, configure the credentials in Uptime Kuma's authentication settings for the monitor.
Scrape the /metrics endpoint (always unauthenticated, even when Basic Auth is enabled):
# prometheus.yml
scrape_configs:
- job_name: 'tdmeter'
static_configs:
- targets: ['tdmeter:2112']| Endpoint | Auth | Method | Description |
|---|---|---|---|
/ |
π Optional | GET | Web dashboard (HTML) |
/api/status |
π Optional | GET | All proxy statuses as JSON |
/health/{name} |
π Optional | GET | Single proxy health (200/503) |
/metrics |
π Open | GET | Prometheus metrics |
/logo.png |
π Open | GET | Embedded logo image |
π Optional = protected only when
web.auth.usernameandweb.auth.passwordare configured. π Open = always unauthenticated (so Prometheus can scrape without credentials).
GET /api/status
{
"proxies": [
{
"name": "proxy-eu-1",
"server": "proxy1.example.com",
"port": "443",
"status": "online",
"latency_ms": 142.5
},
{
"name": "proxy-us-1",
"server": "proxy2.example.com",
"port": "8443",
"status": "offline",
"latency_ms": -1
}
],
"last_check": "2025-01-15T12:00:05Z"
}| Metric | Type | Labels | Description |
|---|---|---|---|
tdmeter_proxy_up |
Gauge | name, server, port |
1 if online, 0 otherwise |
tdmeter_proxy_degraded |
Gauge | name, server, port |
1 if degraded, 0 otherwise |
tdmeter_proxy_latency_ms |
Gauge | name, server, port |
RTT in milliseconds, -1 if unreachable |
tdmeter_check_duration_seconds |
Gauge | β | Wall-clock time of entire check round |
tdmeter_proxies_total |
Gauge | status |
Count of proxies by status (online/degraded/offline) |
# Proxy availability (1 = up, 0 = down)
tdmeter_proxy_up{name="proxy-eu-1"}
# Average latency across all online proxies
avg(tdmeter_proxy_latency_ms > 0)
# Count of offline proxies
tdmeter_proxies_total{status="offline"}
# Check round duration
tdmeter_check_duration_seconds
docker build -t tdmeter .docker run -d \
-v $(pwd)/config.yaml:/etc/tdmeter/config.yaml:ro \
-p 2112:2112 \
--name tdmeter \
--restart unless-stopped \
tdmeterdocker run -d \
-v $(pwd)/config.yaml:/etc/tdmeter/config.yaml:ro \
-e TDMETER_AUTH_USERNAME=admin \
-e TDMETER_AUTH_PASSWORD=supersecret \
-p 2112:2112 \
--name tdmeter \
--restart unless-stopped \
tdmetertdmeter/
βββ main.go # π Entrypoint, HTTP server, signal handling
βββ config/
β βββ config.go # βοΈ YAML + env var config loading & validation
βββ checker/
β βββ checker.go # π Status types, Checker interface, DetermineStatus
β βββ tcp.go # π TCP connectivity checker
β βββ tdlib.go # π‘ TDLib MTProto proxy checker (build tag: tdlib)
βββ scheduler/
β βββ scheduler.go # β±οΈ Periodic check orchestrator with bounded concurrency
βββ metrics/
β βββ metrics.go # π Prometheus gauge registration & updates
βββ web/
β βββ handler.go # π₯οΈ Dashboard, API, health, and logo handlers
β βββ auth.go # π Basic auth middleware
β βββ store.go # πΎ Thread-safe status store
β βββ embed.go # π¦ Embedded templates & logo (go:embed)
β βββ logo.png # π¨ Dashboard logo
β βββ templates/
β βββ index.html # π₯οΈ Alpine.js dashboard template
βββ config.example.yaml # π Example configuration
βββ Dockerfile # π³ Multi-stage build (TDLib + Go + Alpine)
βββ README.md # π You are here
Contributions are welcome! If you want to help:
- Fork the repository
- Create a branch for your changes
- Make and test your changes
- Create a Pull Request
MIT
