Skip to content
Alexey Dolotov edited this page Apr 25, 2026 · 2 revisions

A collection of recurring questions, errors, and confusions taken straight from the issue tracker. Each entry links to the original thread so you can read the full context.

If your problem is not here, also see:


Secrets and links

Q1. How do I generate a secret? What does the hostname mean?

The hostname in the secret is the fronting domain: the SNI mtg announces in its FakeTLS handshake, and the host it connects to when domain fronting kicks in.

mtg generate-secret your.domain          # base64 form
mtg generate-secret --hex your.domain    # hex form (starts with ee...)

The two forms encode the same secret; use whichever your client wants. Pick a domain that resolves to your proxy server's IP — see Q2.

References: #246, #118, #237.

Q2. mtg doctor says "Hostname … is resolved to … addresses, not "

Symptom. Validate SNI-DNS match ❌, even though the proxy itself works.

Cause. You generated the secret with someone else's domain (google.com, storage.googleapis.com, etc.). mtg's FakeTLS ServerHello says "I am google.com" but the IP belongs to your VPS — a trivial mismatch any DPI box can spot.

Fix. Use a domain you own (or a free one like *.duckdns.org, *.nip.io) and point its A/AAAA records at your VPS, then regenerate the secret with that domain. mtg doctor will then pass and mtg run will no longer warn at startup (#461).

References: #444, Surviving Active Probing.

Q3. Can I reuse my v1 secret in v2?

No. v2 only supports FakeTLS (ee…) secrets. Plain (dd…) and secured-only secrets from v1 are rejected:

mtg: error: incorrect secret: incorrect first byte of secret: 0xd9

Generate a new FakeTLS secret with mtg generate-secret. See #286, #285.

Q4. Does mtg work with the @MTProxybot for sponsored channels?

Partially. The bot expects a 32-hex-character secret — that's the legacy format mtg no longer supports. You can still register the proxy host and port with the bot for ad-tag purposes; the bot complaining about the secret length is normal. See #295, #36.


Handshake errors

Q5. cannot read client hello: invalid type: unknown type 0x5

Symptom. Errors only from one platform (often Android), other clients connect fine.

Cause. A buggy Telegram client release that ships a broken or non-standard ClientHello — the canonical example is Telegram Desktop 6.7.2 (tdesktop#30513), and there were similar regressions on Android in the past (#132, #41).

Fix. Update the client. This is not an mtg bug. References: #325, #387.

Q6. failed first bytes of tls handshake (only on Windows / Android)

Symptom. Mac/iOS connect, Windows/Android don't.

Cause. TLS-fragmented ClientHello. DPI-bypass tools (ByeDPI and similar) and some platforms split the handshake across multiple TLS records; older mtg versions assumed the whole ClientHello arrives in a single record and failed HMAC verification.

Fix. Upgrade to the latest 2.x. Issue #431 added record reassembly (reassembleTLSHandshake), refactored in #433. Original report: #323.

Q7. failed tls handshake: bad time / incorrect timestamp … diff=Xs

Symptom. Client connects, gets disconnected after a short while; log shows clock-drift complaints.

Cause. mtg's FakeTLS embeds a timestamp and rejects messages that fall outside tolerate-time-skewness (default 5s, see example.config.toml). Either the server clock is wrong (NTP not running) or the client is misbehaving.

Fix.

  • Verify the server time: timedatectl, chronyc tracking.
  • Bump the tolerance if you have legitimately drifty clients: tolerate-time-skewness = "30s".

References: #155, #226, #161.

Q8. cannot parse client hello when I curl https://my-mtg

This is expected. curl sends a real TLS ClientHello, mtg recognises it as not-MTProto, and forwards the connection to the fronting domain (domain fronting fallback in mtglib/proxy.go). If the fronting domain's TLS terminates with a different cert, curl will complain. Use -k for testing. See #280.


Network: DNS, IPv6, fronting

Q9. cannot resolve dns names: cannot find any ips for tcp:tls:google.com

Symptom. Repeated cannot dial to the fronting domain warnings in logs.

Cause. mtg uses DNS-over-HTTPS (default Cloudflare) and ignores the system resolver. If https://1.1.1.1 is blocked from your VPS (common in some jurisdictions), every fronting lookup fails.

Fix. Configure a reachable resolver in [network]:

[network]
dns = "https://8.8.8.8"        # or tls://1.1.1.1, or udp://9.9.9.9, or "" for system

References: #456, #316, #301.

Q10. mtg doesn't listen on IPv6bind-to = ":443" errors

Symptom. error: incorrect bind-to parameter: empty host: :443 or too many colons in address.

Cause / fix. mtg requires an explicit host part. For dual stack:

bind-to = "[::]:443"     # listens on both IPv4 and IPv6 on Linux

Bare :443 is rejected. References: #308, #298, #460.

Q11. CPU spikes to 100% on hosts with broken IPv6

Symptom. Logs full of dial tcp6 [2a0a:f280:…]:443: cannot assign requested address, thousands of stuck connections, CPU pegged.

Cause. Partial IPv6: the host has an IPv6 address but Telegram's v6 prefixes are unreachable. mtg keeps retrying.

Fix. Force IPv4 in the config (the literal value accepted by mtg's TypePreferIP, see internal/config/type_prefer_ip.go):

prefer-ip = "only-ipv4"

If the symptoms persist, upgrade — #464 fixed the connection-leak loop. Related: #475.

Q12. cannot dial to 203 dc: no addresses / i/o timeout to 91.105.192.100

Symptom. Most chats work, some media fails to load; warnings mention DC 203.

Cause. DC 203 is Telegram's CDN cluster. mtg's mtglib/internal/dc/public_config_updater.go only collects addresses for DC 203 (case 203: // CDN DC); the test fixtures pin 91.105.192.100:443 (v4) and [2a0a:f280:0203:000a:5000::100]:443 (v6). If those endpoints are unreachable from your ASN, every CDN lookup fails.

Fix.

  • Make sure outbound 443/TCP to the CDN endpoints is open.
  • Set allow-fallback-on-unknown-dc = true so mtg routes the request via the default DC instead of failing — mtglib/proxy.go (p.allowFallbackOnUnknownDC branch around line 240) substitutes dc.DefaultDC when no addresses are known. See #216, #210.

References: #369, #310, #208, #407, #470.

Q13. cannot detect public IP address: ifconfig.co is unreachable

Symptom. mtg doctor or mtg run fails to start because it cannot auto-detect the server IP.

Fix. Set the IP yourself in the config (these keys are documented in example.config.toml):

public-ipv4 = "1.2.3.4"
public-ipv6 = "2001:db8::1"

This bypasses ifconfig.co entirely. References: #405, #418.


Defense / blocklists

Q14. Local clients see ip was blacklisted and get routed to the fronting domain

Symptom. Connecting from the same LAN as the proxy (phone on home Wi-Fi → home server) silently fails — the client gets the fronting website instead of Telegram. Logs show {"level":"info","ip":"10.x.x.x","logger":"proxy","message":"ip was blacklisted"}.

Cause. The default [defense.blocklist] URL (firehol_level1.netset) is a bogon list; it includes RFC1918 ranges (10/8, 172.16/12, 192.168/16). Your LAN IP matches and gets blocked.

Fix. Either disable the blocklist:

[defense.blocklist]
enabled = false

…or pick a narrower list (e.g. firehol_abusers_1d), or use hairpin NAT so LAN clients hit the public IP. See #466, #467.

Q15. Replay-protection: how it works, how to disable for testing

[defense.anti-replay] keeps a stable bloom filter (antireplay/stable_bloom_filter.go, default 1 MiB, DefaultStableBloomFilterErrorRate = 0.001) of ClientHello SessionIDs. When a SessionID is seen twice, mtg logs replay attack has been detected!, emits an EventReplayAttack, and drops the client into the domain-fronting fallback (mtglib/proxy.go, doFakeTLSHandshake). This blocks trivial replays of a captured handshake.

If you need to disable it (e.g. for load testing):

[defense.anti-replay]
enabled = false

Don't disable in production — it's a cheap defense against the most common probing technique. References: #129, #281, #76.

Q16. Provider blocks the proxy even though FakeTLS is on

See the dedicated guide Surviving Active Probing. Short version: make sure your secret's hostname resolves to your VPS's IP, run a real TLS service on the same port via SNI routing, and read #458, #440, #111.


Docker, systemd, ports

Q17. Docker mount fails: invalid mount path: 'config.toml'

Cause. The container path must be absolute.

Fix. Use /config.toml on the right side of -v:

docker run -d -v "$PWD/config.toml:/config.toml" \
    -p 443:3128 --name mtg-proxy --restart=unless-stopped \
    nineseconds/mtg:2

References: #275, #287, #296.

Q18. docker run exits silently — how do I generate a secret in Docker?

The default container entrypoint is mtg run, which needs a config; if none is mounted it dies. For one-off commands, override:

docker run --rm nineseconds/mtg:2 generate-secret --hex your.domain
docker run --rm -v "$PWD/config.toml:/config.toml" \
    nineseconds/mtg:2 doctor /config.toml
docker exec mtg-proxy /mtg access /config.toml

Note the /mtg (the binary) inside docker exec, not bare mtg. See #246, #296, #313.

Q19. systemd-managed mtg crashes under load: too many open files

Symptom. A few hundred concurrent users and accept: too many open files appears; the proxy stops accepting connections.

Cause. DynamicUser=true runs the service with the default per-service LimitNOFILE, which is too low for a busy proxy.

Fix. The README's reference unit (/etc/systemd/system/mtg.service) sets LimitNOFILE=65536:

[Service]
ExecStart=/usr/local/bin/mtg run /etc/mtg.toml
Restart=always
RestartSec=3
DynamicUser=true
LimitNOFILE=65536
AmbientCapabilities=CAP_NET_BIND_SERVICE

systemctl daemon-reload && systemctl restart mtg. Raise it further if you see the error again at higher concurrency. See #388, #378, #258.

Q20. Can mtg share port 443 with nginx / Caddy / Traefik?

Not directly — only one process can bind(443). Put an SNI router in front:

  • nginx stream with ssl_preread,
  • HAProxy with req_ssl_sni ACL,
  • sslh in transparent mode,

…that routes your-mtg-domain to mtg and everything else to your web server. A docker-compose example with HAProxy + Caddy lives in contrib/sni-router/ (#462). See SNI Router Setup for a walkthrough. Background: #312, #162, #152.


Performance and operations

Q21. SOCKS5 upstream is configured but mtg ignores it

Symptom. proxies = ["socks5://…"] set under [network], but traffic still leaves directly and mtg doctor shows the proxy as unused.

Cause. Pre-fix, the v1 network package wrapped only some call paths; network.Dial and network.MakeHTTPClient skipped the SOCKS5 proxy and dialled directly.

Fix. Upgrade to a release that contains #367 (merged 2026-03-16), which routes both Dial and MakeHTTPClient through the configured proxy — see network/v2/. Config shape (note proxies belongs under [network]):

[network]
dns = "https://1.1.1.1"
proxies = ["socks5://user:pass@host:1080"]

Verify in debug logs: "proxies":["socks5://…"]. See #468, #350, #315, #392.

Q22. Slow uploads / videos won't load / iOS stuck

Symptom. Text and images fine, video and large file uploads stall.

Cause history.

  • 2.1.3–2.1.4: a buffer-management regression broke uploads (#234, #233).
  • 2.1.11: another media regression (#423, #343).
  • DC 203 unreachable (see Q12).

Fix. Upgrade to the latest 2.x — the regressions above were closed by later releases. If still slow, check prefer-ip, idle timeouts, and DC 203 reachability. References: #270, #283, #265, #406.

Q23. Where are the stats? How do I see active connections?

mtg exposes Prometheus metrics by default:

[stats.prometheus]
enabled = true
bind-to = "127.0.0.1:3129"
http-path = "/"
metric-prefix = "mtg"

curl http://127.0.0.1:3129/ shows counters. There is no built-in text dashboard (#473). See Monitoring and Diagnostics for full metric names and a Grafana setup. For statsd see #27, #103.

Q24. How do I print the proxy link / QR code?

mtg access /path/to/config.toml
# inside docker:
docker exec mtg-proxy /mtg access /config.toml

This prints both tg:// and t.me/proxy?… URLs and a QR. References: #159, #372, #190.

Q25. Multiple secrets / multiple ports on one mtg

Not supported in v2 upstream. v1 had multi-secrets mode but it was removed when the codebase was rewritten (#180, #67, #376, #81; rationale in #376).

Two options:

  • mtg-multi — fork that upstream's own README points to for this case. Same Go codebase, additive secret table, drop-in compatible config.
  • Run multiple mtg instances behind the SNI router (Q20) if you'd rather stay on stock upstream.

Build and install

Q26. cannot make from source / module not go-gettable

Cause. Out-of-date Go toolchain or stale module cache. mtg dropped make in favour of mise, and the README's Build from sources section is the canonical recipe.

Fix.

# install mise (the project's tool manager)
curl https://mise.run | sh

# inside the repo
mise install            # picks up Go from .mise.toml + mise.lock
mise tasks run build    # = `go build`, see [tasks.build] in .mise.toml

mtg switched from make to mise (#329). References: #119, #114, #212.

Q27. bind: cannot assign requested address on startup

Symptom. Crash like listen tcp 127.0.0.1:3129: bind: cannot assign requested address.

Cause. Either the stats port (default 127.0.0.1:3129) is already in use, or bind-to references an IP that doesn't exist on this host (e.g. you copied the config from another server).

Fix. Use 0.0.0.0:443 or a real interface IP, and verify nothing else holds the stats port (ss -lntp | grep 3129). References: #167, #106, #316.


Misc / good to know

Q28. How do I rotate the secret?

Stop the proxy, edit secret = "ee…" in the config, restart. Existing clients will need a new link generated by mtg access. Note that the hostname part of a secret is fixed by the domain — to "rotate" you mostly rotate the random bytes, not the host. See #300, #313.

Q29. Does mtg implement MTProto v1 or v2?

v2. v1 was never supported in this implementation. See #168.


Asking for help

When opening a new issue, please include:

  • mtg version (mtg --version or container tag);
  • redacted config.toml (drop the secret);
  • output of mtg doctor /path/to/config.toml;
  • a sample of debug logs (debug = true) showing the failure;
  • whether the problem is client-specific (which client, which version, which OS).

That makes triage about ten times faster.

Clone this wiki locally