Monitoring

VPN monitoringUptime monitoringIntegrations

Platform

AI assistantsStatus pagesAPI and CLISecurity

More

PricingFree testerDocsBlog Log in

Guides

How to test every server in a V2Ray subscription

To test a V2Ray subscription, fetch the URL, decode the list of share links inside it, and run a real proxy handshake with traffic for each entry. Checking that the URL loads, or that each port is open, misses most dead servers. A client app can do this from your own network; the thq CLI does it from the command line and prints each failing entry by number.

ST Muntaha9 min read

A subscription URL is one address that hands out many servers. That is convenient for users and awkward for testing: the URL can load fine while some of the servers inside it have been dead for weeks. This post shows what a subscription contains, how to test every entry in it, and how to find the one that fails.

The TunnelHQ details below are from the current release of the thq CLI (0.4.0) and the MCP server, checked against their source on 11 November 2026.

What is inside a subscription URL?

An HTTPS address that returns a list of servers. Most return the share links (vless://, vmess://, trojan://, ss://, hy2://, tuic://) one per line, encoded as one long block of base64. Some return the plain list, a sing-box JSON config, or a Clash YAML file instead.

To see what yours returns, fetch it and decode it:

curl -sSL "https://sub.example.com/sub/abc123" -o sub.txt
head -c 200 sub.txt; echo
base64 -d sub.txt > links.txt
grep -cE '^(vless|vmess|trojan|ss|hysteria2|hy2|tuic)://' links.txt

The last line counts the share links. If base64 -d complains, the body may be URL-safe base64 (it uses - and _ instead of + and /), or the = padding at the end may be missing. Translating the characters usually fixes the first case:

tr '_-' '/+' < sub.txt | base64 -d > links.txt

Each line in links.txt is a complete share link. The text after # is only the display name your users see in their app.

Which formats the TunnelHQ CLI and MCP server can read:

Format What the body looks like thq test and the local MCP
Base64 list One long line of letters, digits, + and / Yes
URL-safe base64 list The same, with - and _ Yes
Plain list Lines starting with vless:// and so on Yes
sing-box JSON {"outbounds": [...]} Yes, for VLESS, VMess, Trojan, Shadowsocks, Hysteria2 and TUIC outbounds
Clash YAML proxies: followed by a list No

The CLI also refuses a body over 1 MB, since a real subscription is a few KB to tens of KB.

How do I test every server in a subscription at once?

Use a tool that fetches the URL, splits it into entries, and runs a real handshake with traffic for each one. A client app can do it from your own network, and thq test does it from a terminal.

In a client app. Update the subscription so the list is current, select the servers, and run the real delay test: Test real delay in v2rayN, Test real delays in v2rayNG. Every server gets a time or a -1. This is a fair first look, with two limits. It only tests from your network, and each app's test has its own quirks: v2rayNG, for example, gives each server 1 second to accept a TCP connection before it tries anything else. What the real delay test measures explains those quirks and what -1 means.

With the thq CLI. Pass the subscription URL to thq test, and it tests every entry:

thq test https://sub.example.com/sub/abc123

The CLI fetches the URL from your machine, then sends each entry to TunnelHQ's checkers, five at a time. Each checker runs the real handshake and sends HTTP requests through the tunnel. A line appears as each entry finishes, and a summary comes at the end. The next sections cover reading it.

Not with the web tester. The free tester takes one share link or one config file at a time. It does not take a subscription URL: if you paste an https:// address, it says "Not recognised. Paste a share link or a config file." To use it, decode the subscription as shown above and paste the links one by one. For a whole subscription, the CLI or the MCP server is the better fit.

Why can a reachable node still be dead?

Because "reachable" usually means a port answered. A node is only alive if the proxy accepts your credentials and passes traffic, and a port check tests neither.

Check Passes when Still passes when
The subscription URL loads The panel or web server is up Every server in it is down
TCP port check Something listens on the port The user is deleted, the keys changed, the transport is wrong
TLS check The certificate is valid The same; and a REALITY server completes TLS for anyone
Handshake and traffic The proxy accepted the user and a request came back Rarely: only problems that appear later or elsewhere

The common causes of a reachable but dead node:

  • The user no longer exists, or hit its traffic quota or expiry date on the panel. The server refuses the UUID after a normal TCP and TLS start.
  • The keys changed on the server, but the subscription still hands out the old ones. Every user of that node fails at once.
  • The server can't reach the internet. Its DNS or outgoing route broke. The handshake works, and no request ever comes back.
  • REALITY hides the failure. A REALITY server forwards a connection that doesn't authenticate to its target website, so a TLS check against it always succeeds.

Monitoring V2Ray subscription URLs tells the story of a fleet where the URL check stayed green for months while 47 of 200 servers were dead. For a single key, the VLESS key checker guide goes through the layers one at a time.

How do I find which entry in the list is failing?

Test the entries in the order the subscription lists them, then match the failing number back to its name. thq test numbers each entry and prints its name, so you can retest one entry on its own with --index.

A run prints one line per entry, like this:

  ✔ [ 1/12] # 1  vless        de-fra-01  142ms
  ✖ [ 2/12] # 2  vless        de-fra-02  <the checker's reason>
  ✔ [ 3/12] # 3  trojan       nl-ams-01  98ms

The first number is progress, the # number is the entry's place in the list, and the lines come in the order the tests finish. The name is the text after # in the share link, or the host and port when there is none. To look at one entry closely, test it alone. You get the full single-test output, with the error category and the engine used:

thq test https://sub.example.com/sub/abc123 --index 2

One thing to watch when you compare with your own decoded list. The CLI numbers only the entries it can test. Lines it doesn't support are skipped, and the header says how many ((2 unsupported lines skipped)). If that line appears, count #2 among the supported links, not among all the lines in the file. Numbering your decoded file the same way makes this easy:

grep -nE '^(vless|vmess|trojan|ss|hysteria2|hy2|tuic)://' links.txt | cut -c1-90

Then read the failures as a pattern, not one by one:

  • Every entry on the same host fails. That server is down, or its IP is blocked.
  • Every entry fails. Suspect the subscription or the account first: an expired user, or keys rotated everywhere at once.
  • One protocol fails everywhere. The summary's By protocol list shows this. It usually means a server-side change to that inbound.
  • One entry fails alone. Compare its link with a working one on the same server. The difference is usually a transport field, a flow or an SNI.

How do I test a subscription from the command line?

Install thq, sign in, and run thq test with the URL. Add --first or --index to test part of the list, and --json for scripts.

curl -fsSL https://tunnelhq.com/install.sh | bash
thq login

thq test https://sub.example.com/sub/abc123             # every entry
thq test https://sub.example.com/sub/abc123 --first 5   # the first 5 only
thq test https://sub.example.com/sub/abc123 --index 3   # just the 3rd
thq test https://sub.example.com/sub/abc123 --json > results.jsonl

The summary at the end of a normal run shows how many entries were reachable and unreachable, the fastest, median and slowest latency, a By protocol breakdown when there is more than one protocol, and the five fastest healthy entries.

With --json, the CLI prints one JSON object per entry, in the order they finish. Each has index and label, plus the test result: status (healthy or down), latency_ms, connect_ms and, for a failure, error_message. latency_ms is one round trip through the tunnel, and connect_ms is how long the tunnel took to come up. To list the failures:

jq -r 'select(.status != "healthy") | "\(.index)\t\(.label)\t\(.error_message // .message)"' results.jsonl

The exit code differs between the two modes, which matters in CI:

Mode Exits with 1 when
Normal output No entry passed
--json Any entry failed

Other details worth knowing:

  • Each entry gets 30 seconds by default. --timeout sets it, from 5 to 120.
  • The CLI fetches the subscription itself, from your IP address, with the User-Agent thq-cli/subscription. If your panel filters requests, allow that.
  • The CLI needs a plan with API access, Starter and above. These tests count toward your API rate limit, not your on-demand test quota. See the CLI docs.

From an AI assistant. The TunnelHQ MCP server can do the same through its test_batch tool, which takes subscription URLs along with share links, files and folders. This works with the MCP server you run on your own machine. The hosted server at mcp.tunnelhq.com can't open links for you, so there you pass the share links themselves, or add the URL as a subscription in the dashboard. Ask something like "Test every server in https://sub.example.com/sub/abc123 and list the ones that fail." The MCP docs cover setup.

How do I get alerted when a node in a subscription dies?

Add the URL as a subscription in TunnelHQ. It syncs the URL on a schedule, creates a monitor for each server inside, tests every one at each sync, and alerts on them like any other monitor.

Paste the subscription URL into Add tunnel in the dashboard. It detects the subscription, previews the servers behind it, and asks for these settings:

Setting Options
Sync interval Every 5, 15 or 30 minutes, or 1 (the default), 2, 6, 12 or 24 hours
Engine Auto (Both), the default, which races sing-box and Xray-core; or one of them
Timeout How long each server's test may take; default 30 seconds
Max workers How many servers are tested in parallel; default 5

The servers' monitors are tested at every sync, so their check interval is the sync interval. When the provider adds a server, the next sync adds its monitor. When a server drops out of the subscription, its monitor is paused, so you don't get alerts for a server you retired. If the URL itself keeps failing to load, TunnelHQ retries less often and turns the subscription off after 7 days of continuous failures.

Starter includes 3 subscription URLs, Pro 10 and Business unlimited; the Free plan can't add them. You can also manage them through the API (POST /subscriptions) or the MCP server's create_subscription and sync_subscription tools. Import monitors has the full settings, and VLESS monitoring explains what each check does.

A client app's test and a one-off thq test both answer "does it work right now, from here". A subscription monitor answers the question operators actually have: which server stopped working, and when.