Guides, Nov 10, 2026

# 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 Muntaha, 9 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](https://tunnelhq.com/blog/v2ray-real-delay-test/) 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](https://tunnelhq.com/test/) 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](https://tunnelhq.com/blog/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](https://tunnelhq.com/blog/how-to-check-vless-key/) 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](https://tunnelhq.com/docs/cli/).

**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](https://sub.example.com/sub/abc123) and list the ones that fail." The [MCP docs](https://tunnelhq.com/docs/mcp/) 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](https://tunnelhq.com/docs/import/) has the full settings, and [VLESS monitoring](https://tunnelhq.com/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.

Source: https://tunnelhq.com/blog/test-every-server-in-a-subscription/
