Headless Throne
Run Throne without a window on a server, a router, a Raspberry Pi or in a container, and control it with thronectl.
Throne --headless runs Throne without a window: on a server, a router, a Raspberry Pi, a NAS or in a container. It is the same program as the desktop app, with the same core, profiles, routing profiles and settings, but it shows no window, tray icon or dialog. You control it from a terminal with thronectl, through the Throne API, which headless Throne turns on at the first start.
Headless Throne is available since Throne 1.4.0 on Linux, macOS and Windows. Throne for Android has no headless mode.
Typical uses:
- A proxy box. Throne runs a profile and other devices on the network use its mixed (HTTP and SOCKS) inbound. See Share the proxy with other devices.
- A gateway. Throne runs in TUN mode on a box that routes traffic for the network.
- A container. The Docker image runs Throne with its data in a volume.
- Your own computer, without the window. A systemd user unit keeps your desktop profiles connected after you log out.
The pages in this section:
- Installation & Services: Linux packages, Docker, macOS and the Windows service.
- Managing with thronectl: connect
thronectl, add subscriptions, move a desktop setup over, change settings. - Command Line & Data Directory: every option and variable, where the data lives, restarts and exit codes.
- Health Checks & Logs:
Throne --check,Throne --health, systemd status and logs. - Lockout Protection & Recovery: how Throne keeps the API within reach, and what to do when settings lock you out.
- Troubleshooting: common problems and their fixes.
Quick start
On Debian 12, Ubuntu 22.04 or newer (amd64):
-
Download
Throne-<version>-debian-amd64.debfrom the releases page, or add the APT repository. -
Install the package and start the service:
sudo apt install ./Throne-<version>-debian-amd64.deb sudo systemctl enable --now throne systemctl status thronesystemctl statusshows what Throne is doing, for exampleStatus: "Idle". -
Throne keeps its data in
/var/lib/throneand its API key in/var/lib/throne/config/api.key, which only root can read. Copy the key to a file of your own:mkdir -p ~/.config/throne sudo install -m 600 -o "$USER" /var/lib/throne/config/api.key ~/.config/throne/api-key export THRONE_KEY_FILE=~/.config/throne/api-key -
Install
thronectl. It is not part of the Throne packages: download it from the Throne-api releases, or see Install thronectl. -
Add your subscription and connect:
thronectl status thronectl sub add 'https://sub.example.com/abc' --name Provider thronectl profiles thronectl connect 12Replace
12with the ID of a profile from thethronectl profileslist, or usethronectl connect bestto pick the one with the lowest latency.
Throne now runs the profile, and starts it again after every reboot until you run thronectl disconnect. To check that the box can run Throne at all before you start it, run sudo /opt/Throne/Throne --check. See Throne --check.
How headless Throne works
Throne never sends itself to the background. A service manager starts it at boot, starts it again after a failure and stops it: systemd on Linux, launchd on macOS, the Service Control Manager on Windows, or Docker. The Throne repository has a ready file for each:
| File | What it is |
|---|---|
dist/linux/throne.service |
systemd system unit, runs as root. The .deb and .rpm packages install it as /usr/lib/systemd/system/throne.service. |
dist/linux/throne.env |
Environment file for that unit, installed as /etc/throne/throne.env. Every variable in it is a commented-out example. |
dist/linux/caps.conf |
Optional drop-in: run the unit as the user throne with capabilities instead of root. |
dist/linux/throne-user.service |
systemd user unit, without root. |
dist/macos/moe.Throne.headless.plist |
launchd LaunchDaemon. |
dist/windows/install-service.bat, uninstall-service.bat |
Install and remove the Windows service. |
docker/compose.yaml |
Docker Compose example for the image ghcr.io/throneproj/throne. |
What happens when headless Throne runs:
- It keeps its profiles, groups, routing profiles and settings in a data directory, in the same
throne.dbdatabase as the desktop app. - The Throne API listens on
127.0.0.1:9095from the first start, with a key that Throne generates intoconfig/api.keyin the data directory.thronectluses that key. See Connect thronectl to Throne. - It remembers the running profile and whether TUN was on, and starts them again at every start until you disconnect.
--no-resumeturns that off for one run. - It reports its state to the service manager and watches its own progress. When the core keeps failing, a profile hangs or the main loop stops, it exits with a code that tells the service manager to start it again. See Exit codes.
Throne --healthasks the running Throne how it is doing, andThrone --checktells you whether the computer can run it. See Health Checks & Logs.
Differences from the desktop app
Headless Throne asks nothing. What the desktop app asks in a dialog, headless Throne decides on its own or writes to the log:
| Situation | Headless Throne |
|---|---|
| A notice or an error | Writes it to the log. |
| TUN needs privileges | Refuses with needs_privileges. Throne --check says what is missing. |
| An Xray profile needs the geoip or geosite files | Downloads them and tries the start once more. |
| The kill switch cannot work | Logs why, reports kill_switch_failed, and refuses to start profiles. |
| A VPN profile asks for a one-time code | Answers it from the profile's OTP account, if it has one. |
| A VPN profile asks for anything else, such as a web sign-in | Cancels the question when it expires, after 5 minutes at most (vpn_challenge_pending). Saved credentials that the server rejects give vpn_auth_failed. |
| A setting needs a program restart | Logs it. It applies after the next restart (thronectl restart). |
| The WARP terms of service | Never accepts them. |
These features need a window and are not available in headless Throne:
- The system proxy (
thronectl proxy onfails withsystem_proxy_unsupported), the tray icon, global hotkeys and the editors. - The built-in updater, and restarting elevated or with TUN. Update through the package, the Docker image or the installer.
throne://deep links and file associations. Import withthronectl importinstead.- Sign-in prompts of VPN profiles, such as SSO, beyond one-time codes from an OTP account.
- WARP registration.
- Settings that the API does not manage, and restoring Extra Core profiles through the API.
Set these things up in a desktop Throne and move them over with a backup. For a systemd user unit, stop the unit and open the desktop app: both use the same data.
Running beside a desktop Throne
TUN's interface (throne-tun), the core's DNS port (dns_in_port, 5533 by default) and TUN's routes belong to the whole computer, so only one Throne at a time can run TUN on it. Headless Throne turns TUN on only when:
- no other
throne-tuninterface exists (checked on Linux and Windows), - no network interface already holds the TUN address (
tun_ipv4_cidr,172.19.0.1/24by default), and - the DNS port is free while no profile runs.
Otherwise thronectl tun on fails with tun_conflict, and thronectl status shows what holds TUN. When TUN cannot come back at start, Throne --health reports tun_unavailable. If a Docker bridge or another network uses Throne's TUN range, move TUN to a free one, then turn it on again:
thronectl settings set tun_ipv4_cidr=172.29.0.1/24
thronectl tun on
When another program holds the DNS port, give Throne a free port with thronectl settings set dns_in_port=PORT.
Headless Throne also marks that it runs: in /run/throne as root (/var/run/throne on macOS), in $XDG_RUNTIME_DIR/throne for a user, and on Windows as Global\ThroneHeadless (Local\ThroneHeadless when it is neither elevated nor a service, seen only in the same Windows session). A desktop Throne on the same computer then:
- warns at start that a headless Throne runs,
- refuses TUN while the headless one runs, and
- does not update itself while the headless one runs from the same installation.
Two Thrones never share a data directory. If a headless user unit uses the desktop app's data directory, the desktop app says that Throne is running in the background and exits. Stop the unit first with systemctl --user stop throne. A second headless Throne with its own data directory can run beside the first; the checks above keep the two from running TUN at the same time.
Device ID
Some subscriptions ask for a device ID (the x-hwid header, see User-Agent and HWID). On Linux, Throne takes it from /etc/machine-id, else from /var/lib/dbus/machine-id. A system without either file, such as a container, gets a generated ID in config/hwid at the first start. That ID stays with the data directory.
The official Docker image ships without a machine ID, so every container gets its own. If you build your own image, do not put a machine-id file into it: every container made from the image would send the same ID.
macOS uses the hardware UUID, and Windows uses the MachineGuid from the registry.