Secrets & Limits¶
Generating Secrets¶
Generate a random 16-byte secret:
For fake-TLS mode, pass the domain to get the full ee-prefixed secret ready for client links:
teleproxy generate-secret www.google.com
# stdout: ee<secret><domain-hex> (use in tg://proxy links)
# stderr: Secret for -S: <secret> (use with -S flag)
Multiple Secrets¶
Up to 16 secrets, each with an optional label:
Secret Labels¶
Labels identify which secret a connection uses.
CLI:
Installer:
curl -sSL .../install.sh | \
SECRET_1=cafe...ab SECRET_LABEL_1=family \
SECRET_2=dead...ef SECRET_LABEL_2=friends \
sh
Docker:
# Inline labels
SECRET=cafe...ab:family,dead...ef:friends
# Or numbered
SECRET_1=cafe...ab
SECRET_LABEL_1=family
SECRET_2=dead...ef
SECRET_LABEL_2=friends
Labels appear in:
- Logs:
TLS handshake matched secret [family] from 1.2.3.4:12345 - Prometheus:
teleproxy_secret_connections{secret="family"} 3 - Stats:
secret_family_connections 3
Unlabeled secrets are auto-labeled secret_0, secret_1, etc.
Label rules: max 32 chars, alphanumeric plus _ and -.
Per-Secret Connection Limits¶
Prevent a leaked secret from consuming all resources:
# CLI: append :LIMIT after the label
./teleproxy ... -S cafe...ab:family:1000 -S dead...ef:public:200
# Without a label
./teleproxy ... -S cafe...ab::500
Installer:
curl -sSL .../install.sh | \
SECRET_1=cafe...ab SECRET_LABEL_1=family SECRET_LIMIT_1=1000 \
SECRET_2=dead...ef SECRET_LABEL_2=public SECRET_LIMIT_2=200 \
sh
Docker:
# Environment variables
SECRET_LIMIT_1=1000
SECRET_LIMIT_2=200
# Inline
SECRET=cafe...ab:family:1000,dead...ef:public:200
When the limit is reached:
- Fake-TLS (EE): rejected during TLS handshake — client sees the backend website
- Obfuscated2 (DD): connection silently dropped
Multi-worker note: with -M N workers, each enforces limit / N independently.
Metrics:
- Stats:
secret_family_limit 1000,secret_family_rejected 42 - Prometheus:
teleproxy_secret_connection_limit{secret="family"} 1000,teleproxy_secret_connections_rejected_total{secret="family"} 42
Per-Secret Quotas¶
Cap total bytes transferred (received + sent) per secret. Once exhausted, active connections are closed and new connections are rejected.
TOML config:
[[secret]]
key = "cafe...ab"
label = "guest"
quota = "10G" # accepts: bytes (int), or "500M", "10G", "1T"
Docker:
Quota is cumulative since startup — it does not reset on SIGHUP config reload. Restart the proxy to reset usage.
Metrics:
- Stats:
secret_guest_quota 10737418240,secret_guest_bytes_total 5368709120,secret_guest_rejected_quota 3 - Prometheus:
teleproxy_secret_quota_bytes{secret="guest"} 10737418240,teleproxy_secret_bytes_total{secret="guest"} 5368709120
Per-IP Rate Limiting¶
Cap real-time throughput per source IP. Uses a token bucket algorithm — each IP gets a bucket that refills at the configured rate. When the bucket is empty, reads are paused until tokens refill. Unlike quota (which closes connections), rate limiting throttles via TCP backpressure — users see slower speeds, not dropped connections.
TOML config:
[[secret]]
key = "cafe...ab"
label = "shared"
rate_limit = "10M" # 10 MB/s per IP (accepts: bytes/sec int, or "500K", "10M")
Docker:
The rate limit is combined (received + sent) per source IP. Burst size is 1 second of tokens — a new connection can burst up to the rate limit before throttling kicks in.
Multi-worker note: with -M N workers, each enforces rate_limit / N independently.
Reloadable: changing rate_limit on SIGHUP takes effect immediately for new data.
Metrics:
- Stats:
secret_shared_rate_limit 10485760,secret_shared_rate_limited 42 - Prometheus:
teleproxy_secret_rate_limit_bytes{secret="shared"} 10485760,teleproxy_secret_rate_limited_total{secret="shared"} 42
Per-Secret Unique IP Limits¶
Cap how many distinct client IPs can use a secret simultaneously. Additional connections from an already-connected IP are allowed.
TOML config:
Docker:
Metrics:
- Stats:
secret_guest_max_ips 5,secret_guest_unique_ips 3,secret_guest_rejected_ips 0 - Prometheus:
teleproxy_secret_max_ips{secret="guest"} 5,teleproxy_secret_unique_ips{secret="guest"} 3
Secret Expiration¶
Auto-disable a secret after a timestamp. New connections are rejected; existing connections continue until they close naturally.
TOML config:
[[secret]]
key = "cafe...ab"
label = "temp"
expires = 2025-06-30T23:59:59Z # TOML datetime (UTC)
# or: expires = 1751327999 # Unix timestamp
Docker:
Metrics:
- Stats:
secret_temp_expires 1751327999,secret_temp_rejected_expired 12 - Prometheus:
teleproxy_secret_expires_timestamp{secret="temp"} 1751327999
Graceful Draining on Removal¶
When a secret is removed from the TOML and you kill -HUP the proxy, in-flight
connections that authenticated under that secret keep working — the slot
transitions to a draining state, new connections matching the removed secret
are rejected, and the slot is released only after all existing connections
have closed (or after drain_timeout_secs seconds, whichever comes first).
This enables zero-downtime secret rotation without dropping users mid-session.
TOML config:
Workflow for rotating a secret without dropping users:
- Add the new secret to
config.tomlalongside the old one. kill -HUP $(pidof teleproxy)— both secrets are now active.- Update clients to the new secret at your own pace.
- Remove the old secret from
config.toml. kill -HUPagain — old secret enters draining state, new connections matching it are rejected, in-flight ones keep going until they close.- After all clients have migrated (or
drain_timeout_secselapses), the slot is released and metrics for it disappear.
Re-adding a draining secret before the timeout revives it — the same slot,
counters, and IP tracking carry over. Pinned -S CLI secrets are immutable
across SIGHUP and never drain.
Metrics for a draining secret:
- Stats:
secret_<label>_draining 1,secret_<label>_drain_age_seconds,secret_<label>_rejected_draining,secret_<label>_drain_forced - Prometheus:
teleproxy_secret_draining{secret="..."},teleproxy_secret_drain_age_seconds{secret="..."},teleproxy_secret_rejected_draining_total{secret="..."},teleproxy_secret_drain_forced_total{secret="..."}
Capacity: at most 16 active secrets at any moment, plus up to 16 additional
draining slots. Rotating all 16 secrets twice in quick succession before the
first batch finishes draining will fail the second reload — wait for drain to
complete or set a smaller drain_timeout_secs to reclaim slots faster.
Per-IP Top-N Metrics¶
Surface the heaviest client IPs per secret in /metrics to diagnose rogue clients. Disabled by default — opt in with a top-level config knob, not a per-secret one (cardinality is an operator concern).
TOML config:
Docker:
When enabled, three new Prometheus metric families are emitted, each labelled {secret="...", ip="..."} for the top-N heaviest IPs per secret (sorted by total bytes):
teleproxy_secret_ip_connections— current active connections from this IP (gauge)teleproxy_secret_ip_bytes_received_total— bytes received from this IP (counter)teleproxy_secret_ip_bytes_sent_total— bytes sent to this IP (counter)
Cardinality is bounded at top_ips_per_secret × secret_count labelled samples per family. Counters reset when an IP entry is recycled — Prometheus rate() handles this correctly.
TOML Config Example¶
All per-secret features combined:
[[secret]]
key = "cafe01234567890abcafe01234567890a"
label = "family"
limit = 100
quota = "50G"
rate_limit = "10M"
max_ips = 10
expires = 2026-12-31T23:59:59Z
[[secret]]
key = "dead01234567890abcead01234567890a"
label = "guest"
limit = 50
quota = "5G"
rate_limit = "2M"
max_ips = 3
expires = 2025-06-30T00:00:00Z