# CLI

`thq` is the TunnelHQ command-line tool. Use it to test any VPN config, share link, or subscription URL from your terminal, manage monitors, Uptime checks, and API keys, and connect the MCP server to your AI editor. It runs on macOS, Linux, WSL, and Windows.

This page describes `thq` 0.4.0, the current release. Run `thq --version` to see yours.

## Install

The macOS / Linux installer needs nothing installed first: it brings its own Node.js (downloaded from nodejs.org and checked against its published checksums) and keeps it, with `thq`, in `~/.local/share/tunnelhq`. It links `thq` into `/usr/local/bin` when run as root, otherwise into `~/.local/bin`, which it adds to your PATH if needed. On Windows, and with `npm install -g`, `thq` needs Node.js 20 or newer.

<Tabs syncKey="os">
  <TabItem label="macOS / Linux / WSL">
    ```bash title="Install on macOS, Linux, or WSL"
    curl -fsSL https://tunnelhq.com/install.sh | bash
    ```
  </TabItem>
  <TabItem label="Windows">
    In PowerShell 5.1 or newer:

    ```powershell title="Install on Windows"
    irm https://tunnelhq.com/install.ps1 | iex
    ```

    Or install the package with npm directly:

    ```powershell title="Install on Windows with npm"
    npm install -g https://tunnelhq.com/releases/latest/tunnelhq-cli.tgz
    ```
  </TabItem>
</Tabs>

## Sign in

`thq login` uses the device flow: run the command, approve the request in your browser, and you're signed in. There's no token to copy. Your credentials are saved to `~/.thq/credentials.json`.

```bash title="Sign in"
thq login
```

| Option | Description |
| --- | --- |
| `--no-browser` | Don't open the browser; print the approval URL instead. |
| `--qr` | Print a QR code for the approval URL. On by default when there's no display, for example over SSH. |
| `--no-qr` | Don't print the QR code. |
| `--host <url>` | Sign in to a different API host. |

The approval is refused if your workspace's plan doesn't include API access, with the message "API access isn't included on this workspace's plan. Upgrade to use the CLI."

The key `thq login` creates is an [account-wide key](https://site.dev.tunnelhq.com/docs/api-keys/#kinds-of-key), named "CLI · &lt;computer name&gt; · &lt;date&gt;", which never expires.

`thq logout` removes the saved credentials and switches the key off on the server. The key stays on the **API Keys** page as **Disabled**; to delete it, choose **Revoke** there, or run `thq keys revoke <id> --hard` before you log out.

For CI and scripts, set an API key in the environment instead. When `THQ_API_KEY` is set, it's used instead of the saved sign-in.

```bash title="Use an API key in CI"
export THQ_API_KEY="thk_..."
```

:::caution[Requires a paid plan]
The CLI uses the API, so it needs a plan with API access: Starter and above. See [Getting an API key](https://site.dev.tunnelhq.com/docs/api-keys/).
:::

## Test a config

`thq test` checks a VPN config on the spot and saves nothing. The target can be a config file path, a share link, a raw config string, or an `https://` subscription URL.

```bash title="Test a config, a share link, or a subscription"
thq test ./wg0.conf
thq test 'vless://<uuid>@vpn.example.com:443?security=reality&...'
thq test https://provider.example.com/sub --first 5 --json
```

| Option | Description |
| --- | --- |
| `--json` | Machine-readable output, for CI. |
| `--protocol <name>` | Force the protocol: `wireguard`, `openvpn`, `ikev2`, `openconnect`, `vmess`, `vless`, `trojan`, `shadowsocks`, `amneziawg`, `hysteria2`, or `tuic`. |
| `--timeout <seconds>` | How long to wait. Default 30, minimum 5, maximum 120. |
| `--host <hostname>`, `--port <number>` | Server host and port for IKEv2, OpenConnect, and OpenVPN (required for IKEv2 and OpenConnect). WireGuard, AmneziaWG, and share links use their own. |
| `-u`, `--username`; `-p`, `--password` | Override the credentials. |
| `--remote-id <id>` | Set the IKEv2 remote ID. |
| `--engine <name>` | Force an engine: `singbox`, `xray`, `auto`, `both`, or `race`. By default, the checker picks one per protocol. |
| `--first <n>` | Subscription URLs only: test the first *n* entries. Alias: `--limit`. |
| `--index <n>` | Subscription URLs only: test one entry, counting from 1. |

A subscription URL tests every entry unless you pass `--first` or `--index`.

IKEv2 and OpenConnect don't need a config: pass the server and the credentials.

```bash title="Test IKEv2 without a config"
thq test --protocol ikev2 --host vpn.example.com -u alice -p secret
```

`thq test` works out the protocol from, in order:

1. `--protocol`.
2. The URI scheme, such as `vless://` or `ss://`.
3. The content: an `[Interface]` section is WireGuard, or AmneziaWG when it has `Jc`, `Jmin`, `Jmax`, or `H1`–`H4` keys; `client` and `remote` lines are OpenVPN. So a `.conf` file that holds an OpenVPN config is tested as OpenVPN.
4. The file extension: `.conf` is WireGuard, `.ovpn` is OpenVPN.
5. An `http://` or `https://` address is a subscription URL.

`thq test` exits with `0` when the test passes and `1` when it fails, so you can use it in CI.

Tests run on the same checkers as scheduled monitors, through [`POST /check`](https://site.dev.tunnelhq.com/docs/api/#endpoints). They count toward your API rate limit, not your on-demand test quota.

## Manage monitors

`thq servers` manages the project's saved monitors. Every `thq servers` and `thq checks` command works in the project in `THQ_PROJECT_ID`, or the one you pass with `--project <id>`, and takes `--json`.

| Command | Description |
| --- | --- |
| `thq servers list` | List monitors. `--family` chooses `tunnel` (the default), `uptime`, or `all`. Filter with `--status`, `--protocol`, `--search`, and `--active-only`; `--limit` defaults to 100. |
| `thq servers create <input> --name <name>` | Create a VPN monitor from a config file, share link, or raw config. It takes `thq test`'s connection options (`--protocol`, `--host`, `--port`, `-u`, `-p`, `--remote-id`), plus `--proto udp` or `tcp` for OpenVPN, `--ip` for a WireGuard or AmneziaWG config without an `Endpoint` line, `--interval`, `--tag` (repeat it for more), and `--paused`. For OpenConnect and IKEv2, leave out the input and pass `--host`, `--username`, and `--password`. |
| `thq servers delete <id> --yes` | Delete a monitor or an Uptime check by its ID, `srv_123` or `123`. `--yes` is always required, with `--json` too. |

```bash title="Create monitors"
thq servers create wg.conf --name "WG Frankfurt"
thq servers create 'vless://<uuid>@host:443' --name "VLESS EU"
thq servers create '' --protocol openconnect --name "OC Corp" \
  --host vpn.example.com --username alice --password secret
```

## Manage Uptime checks

`thq checks list` lists the project's [Uptime checks](https://site.dev.tunnelhq.com/docs/uptime/). Filter with `--type`, `--status`, `--search`, and `--active-only`; `--limit` defaults to 100.

`thq checks create <target> --name <name>` creates a check. Without `--type`, the target sets the type, the same way the dashboard does:

| Target | Creates |
| --- | --- |
| `https://example.com` or `example.com/health` | A website check, with `https://` added if it's missing. With `--keyword`, a keyword check. |
| `db.example.com:5432` or `[2001:db8::1]:443` | A TCP port check. `--port` overrides the port. |
| `8.8.8.8` (an IP address) | A ping check. |
| `example.com` (a bare hostname) | A website check, with `https://` added. Add `--type ping` to ping it instead. |
| `1.1.1.1 --domain example.com` | A DNS check. The target is the DNS server; `--keyword` is text the answer must contain. |
| No target, with `--period` | A heartbeat. The command prints the address your job calls. |

Targets must be public addresses: private and reserved ones are refused.

| Option | Description |
| --- | --- |
| `--type <type>` | `http`, `keyword`, `ping`, `port`, `dns`, or `push` (a heartbeat). |
| `--keyword <text>` | Keyword check: text the page must contain. DNS check: text the answer must contain. |
| `--invert-keyword` | Keyword check: alert when the text *is* found. |
| `--accept <codes>` | Website and keyword checks: accepted status codes, such as `200-299,301`. Default `200-299`. |
| `--port <n>` | TCP port check: the port. |
| `--domain <name>` | DNS check: the domain to ask about. |
| `--period <seconds>` | Heartbeat: the longest time between two calls from your job. Default 86400. |
| `--grace <seconds>` | Heartbeat: how late a call can be before it's down. Default 3600. |
| `--interval <seconds>` | Seconds between checks. Left out, the recommended default. |
| `--tag <name>` | Add a tag. Repeat it for more. |
| `--paused` | Create the check paused. |

```bash title="Create Uptime checks"
thq checks create https://example.com --name "Homepage"
thq checks create https://example.com/login --name "Login" --keyword "Sign in"
thq checks create db.example.com:5432 --name "Postgres"
thq checks create 1.1.1.1 --name "Resolver" --domain example.com
thq checks create --name "Nightly backup" --period 86400 --grace 3600
```

To change or pause a check, use the dashboard, the [REST API](https://site.dev.tunnelhq.com/docs/uptime/checks/#uptime-checks-in-the-api), or the [MCP server](https://site.dev.tunnelhq.com/docs/mcp/). To delete one, use `thq servers delete`.

## Manage status pages

Make a [status page](https://site.dev.tunnelhq.com/docs/concepts/status-pages/), choose what it shows, and post incidents and updates from a terminal or a script.

:::note[Coming with the next release]
These commands aren't in `thq` 0.4.0. They come with the CLI's next release. Until then, use the dashboard, the [REST API](https://site.dev.tunnelhq.com/docs/api/reference/operations/list-status-pages/), or the [MCP server](https://site.dev.tunnelhq.com/docs/mcp/) signed in at `mcp.tunnelhq.com`.
:::

| Command | Description |
| --- | --- |
| `thq status-pages list` | List the project's status pages. |
| `thq status-pages create <name>` | Make a page, a draft until you publish it in the dashboard. Without `--services`, `--type`, or `--tag` it shows the suggested set: tunnels and website checks that aren't paused. `--slug <slug>` sets its address, `<slug>.tunnelhq.net`. |
| `thq status-pages services <page> [--add …] [--remove …] [--hide …] [--show …]` | Add, remove, hide, or show many services at once. Removing takes a service off the page; the tunnel or check stays. |
| `thq status-pages domain <page> <domain>` | Put the page on your own domain, such as `status.example.com`, and print the DNS record to add. `--check` looks the record up and switches on HTTPS once it points to TunnelHQ. `--remove` takes the domain off. |
| `thq status-pages brand <page> [--use …]` | Take the logo, dark logo, favicon, and share image from the page's website. `--use` saves only those you name: `logo`, `logo_dark`, `favicon`, `share_image`. |
| `thq status-pages incidents <page> [--status open\|resolved]` | List a page's incidents, with their updates. |
| `thq status-pages open <page> --title <text> --impact <impact> --service <service> --message <text>` | Open an incident. `--impact` is `degraded`, `partial`, or `full`. Repeat `--service` for each affected service. `--status` sets the first status: `investigating` (the default), `identified`, or `monitoring`. |
| `thq status-pages update <page> <incident> --status <status> --message <text>` | Post an update. `--status` is `investigating`, `identified`, `monitoring`, or `resolved`; `resolved` closes the incident. |

Every `thq status-pages` command works in the project in `THQ_PROJECT_ID`, or the one you pass with `--project <id>`, and takes `--json`.

- `<page>` is the page's ID (`sp_12`), its address (`larkspur-vpn`), or its name.
- `--service` is a service's ID (`sps_40`) or its name on the page, including a group's members. If no service matches, the command fails and lists the page's services instead of guessing.
- A monitor for `--services`, `--add`, `--remove`, `--hide`, or `--show` is its ID (`srv_42`) or its name. A group's name or ID brings its members.
- `create --services` also takes `suggested` (the default) or `none`, for an empty page. `--type` and `--tag` add every monitor of a type, or with a tag, beside any you name. The types are `tunnel`, `website`, `keyword`, `ping`, `port`, `dns`, `heartbeat`, and `group`.
- In `services`, an action given without names takes the monitors `--type` and `--tag` pick, as in `--remove --type heartbeat`.

A page shows up to 400 services, a server's tunnels counting once. The commands print the count, such as "103 of 400 services".

```bash title="Make a page, then trim it"
thq status-pages create "Larkspur VPN"
thq status-pages services larkspur-vpn --remove --type heartbeat
thq status-pages services larkspur-vpn --add srv_42 "API health" --hide "Old Frankfurt"
```

```bash title="Put a page on your own domain, with your logo"
thq status-pages domain larkspur-vpn status.larkspur.example
thq status-pages domain larkspur-vpn --check
thq status-pages brand larkspur-vpn
```

```bash title="Open an incident, then resolve it"
thq status-pages open larkspur-vpn --title "OpenVPN failing in Frankfurt" --impact partial --service "OpenVPN Frankfurt" --message "Connections are failing. We are looking into it."
thq status-pages update larkspur-vpn spi_7 --status resolved --message "Connections work again."
```

Publishing, the rest of a page's look, and its password aren't in the CLI: use the dashboard, the [REST API](https://site.dev.tunnelhq.com/docs/api/reference/operations/update-status-page/), or the [MCP server](https://site.dev.tunnelhq.com/docs/mcp/).

## Manage maintenance

Plan [maintenance windows](https://site.dev.tunnelhq.com/docs/concepts/maintenance/) from a terminal or a script. While one runs, checks and alerts pause for what it covers, and your status pages show it.

:::note[Coming with the next release]
These commands aren't in `thq` 0.4.0. They come with the CLI's next release.
:::

| Command | Description |
| --- | --- |
| `thq maintenance list` | List the project's windows, with when each runs, what it covers, and where it stands. |
| `thq maintenance create <title> --start <when> --end <when>` | Plan a window. Name what it covers with `--monitor`, or use `--covers tunnels`, `uptime`, or `all`. |
| `thq maintenance update <window> …` | Change a window: only what you give changes. `--pause` pauses it, `--resume` resumes it, `--title` renames it. |
| `thq maintenance end <window>` | End a running one-time window now. Checks and alerts resume within a minute. |
| `thq maintenance cancel <window> --yes` | Delete a window, planned or running. Without `--yes`, it only says what it would delete. |

- `<window>` is the window's ID (`mnt_4`) or its title.
- A one-time window takes `--start` and `--end` as `YYYY-MM-DDTHH:mm`. With `--days`, it repeats every week on those days, such as `--days mon,wed,fri`, and `--start` and `--end` are `HH:mm`. All seven days means every day.
- `--timezone` is an IANA time zone, such as `Europe/Berlin`. Left out, it's your computer's.
- `--monitor` is a monitor or group, by ID or name. Repeat it for more; a group brings its members.
- `--note` adds a note shown with the window.
- Every `thq maintenance` command works in the project in `THQ_PROJECT_ID`, or the one you pass with `--project <id>`, and takes `--json`.

```bash title="Plan maintenance"
thq maintenance create "Frankfurt upgrade" --start 2026-11-12T02:00 --end 2026-11-12T04:00 --timezone Europe/Berlin --monitor "OpenVPN Frankfurt"
thq maintenance create "Nightly backup" --days mon,wed,fri --start 02:00 --end 02:30 --covers uptime --timezone Asia/Karachi
thq maintenance update "Nightly backup" --pause
```

## Manage alert channels

Choose where a monitor's alerts go, and check that they arrive. New channels are made in the dashboard under **Integrations**, where their secrets are entered.

:::note[Coming with the next release]
These commands aren't in `thq` 0.4.0. They come with the CLI's next release.
:::

| Command | Description |
| --- | --- |
| `thq channels list` | List the project's alert channels, with each one's type and whether it's on. |
| `thq channels show <monitor>` | The channels a check's or tunnel's alerts go to. |
| `thq channels set <monitor> <channel>…` | Set the channels its alerts go to. This replaces its list, so name every channel it should keep. |
| `thq channels set <monitor> --none` | Its alerts go to no channel. |
| `thq channels test <monitor>` | Send a test alert through each of its channels, and show which took it. |

- `<monitor>` and `<channel>` are an ID (`srv_12`, `chn_22`) or a name. If two share a name, use the ID.
- Setting channels and sending a test alert need a role that can edit monitors. A test reaches at most 10 channels, and you can send 10 test messages an hour.
- Every `thq channels` command works in the project in `THQ_PROJECT_ID`, or the one you pass with `--project <id>`, and takes `--json`.

```bash title="Route a check's alerts"
thq channels set "Shop website" "Ops Slack" chn_22
thq channels test "Shop website"
```

## Manage servers

Add [servers](https://site.dev.tunnelhq.com/docs/concepts/servers/), read them, and set their alerts from a terminal or a script.

:::note[Coming with the next release]
These commands aren't in `thq` 0.4.0. They come with the CLI's next release.
:::

| Command | Description |
| --- | --- |
| `thq hosts install` | Print the command that installs the agent. `--name` and `--group` add a name and a group. `--auto-update` or `--no-auto-update` decides automatic updates; without either, the project's default does. `--rotate` makes a new install key; servers already installed keep reporting. |
| `thq hosts list` | List the project's servers. Filter with `--status`, `--group`, `--tag`, `--search`, or `--monitor`, and order with `--sort`. |
| `thq hosts get <id>` | One server in full: readings, system, linked tunnels and checks, alerts, and the uninstall command. |
| `thq hosts history <id>` | A server's readings over time. `--metric` is `cpu` by default, and `--range` is `1h`, `6h`, `24h` (the default), or `7d`. |
| `thq hosts update <id>` | Change `--name`, `--group`, or `--tags`. `""` clears any of them. `--auto-update on` or `off` switches automatic agent updates. |
| `thq hosts update-agent <id>` | Update the server's agent to the latest release now. |
| `thq hosts link <id> <monitor>` | Link a tunnel or check to the server by hand. |
| `thq hosts unlink <id> <monitor>` | Keep a tunnel or check away from the server, even when the address matches. |
| `thq hosts alerts <id>` | Show the server's alerts, or change them with the options below. |
| `thq hosts incidents [<id>]` | Server incidents of the last 30 days, open first. `--open` shows only open ones. |
| `thq hosts remove <id> --yes` | Remove the server from TunnelHQ. Its agent stays installed until you run the uninstall command. |

`thq hosts alerts` changes only what you name:

| Option | Takes |
| --- | --- |
| `--offline-after` | Seconds without a report before it alerts, 90 or more, or `off`. |
| `--service-after` | Seconds a watched service is stopped before it alerts, or `off`. |
| `--cpu`, `--memory`, `--disk` | A percent and seconds, such as `90:600` for above 90% for 10 minutes, or `off`. |
| `--country` | `on` or `off`. |
| `--cert` | `7`, `14`, `21`, or `30` days before the certificate expires, or `off`. Needs agent 1.7.0. |

- `<id>` is a server's ID (`hst_7`, or `7`), and `<monitor>` a tunnel's or check's (`srv_81`).
- Changing a server needs a role that can edit monitors, and removing one a role that can delete monitors.
- Every `thq hosts` command works in the project in `THQ_PROJECT_ID`, or the one you pass with `--project <id>`, and takes `--json`.

```bash title="Watch a server's disk and certificate"
thq hosts alerts hst_7 --disk 85:300 --cert 21
thq hosts list --status offline
```

## Manage API keys

| Command | Description |
| --- | --- |
| `thq keys list` | List your API keys. |
| `thq keys create <name> [--expires <ISO date>]` | Create a workspace key. Keys created this way start with `uk`. Needs a workspace key in `THQ_API_KEY`: the account-wide key from `thq login` is refused. An `--expires` value that isn't a date, such as `31/12/2026`, is refused. |
| `thq keys revoke <id> [--hard]` | Disable a key. With `--hard`, delete it permanently. The ID is the number, or the `uk…` form. |

## Set up the MCP server

`thq mcp` prints the configuration for the [MCP server](https://site.dev.tunnelhq.com/docs/mcp/), or applies it for you.

| Option | Description |
| --- | --- |
| `--client <name>` | `claude`, `cursor`, `windsurf`, or `cline`. |
| `--apply` | Apply the configuration: runs `claude mcp add` for Claude Code, or writes the JSON config for the other clients. |
| `--update` | With `--client`, clear TunnelHQ's cached MCP server and add it to that client again, so your editor gets the current release on its next start. Without `--client`, it changes nothing. |
| `--json` | Print the configuration snippet and exit. |
| `--api-key <value>` | Use this key in the configuration. |

## Other commands

| Command | Description |
| --- | --- |
| `thq whoami` | Show the account you're signed in as. |
| `thq doctor` | Diagnose connection and authentication problems. |
| `thq upgrade` | Update the CLI. It detects whether you installed it with npm, Homebrew, or the install script. Alias: `thq update`. |
| `thq install` | Reinstall the CLI to a stable location and add it to your `PATH`. |

Run `thq --help` for every command and option.

## Interactive REPL

Run `thq` with no arguments in an interactive terminal to open the slash-command interface. It doesn't open when input or output is redirected, or when `NO_COLOR` is set.

| Command | Description |
| --- | --- |
| `/test` | Test a VPN config, share link, or subscription URL. |
| `/whoami` | Show your account, plan, and quota. |
| `/keys` | `list`, `create <name>`, or `revoke <id>`. |
| `/mcp` | Set up the MCP server. |
| `/doctor` | Diagnose connection and authentication problems. |
| `/upgrade` | Update the CLI. |
| `/login`, `/logout` | Sign in or out. |
| `/help` | List every command. |
| `/exit` | Quit. |

## Environment variables

| Variable | Description |
| --- | --- |
| `THQ_API_KEY` | API key to use instead of the saved sign-in. |
| `THQ_HOST` | API host. Defaults to `https://app.tunnelhq.com`. |
| `THQ_PROJECT_ID` | Default project. |
| `THQ_ORG_ID` | Default workspace. |
| `THQ_HEADLESS` | `1` or `0` to force headless mode on or off. |
| `THQ_SKIP_UPDATE_CHECK` | `1` to skip the background check for a newer version. |
| `THQ_INSTALL_DIR` | Where `thq install` puts the CLI. Defaults to `~/.local/bin`, or `%LOCALAPPDATA%\TunnelHQ\bin` on Windows. |
