Quick Start
Quick Start

Quick Start

r6me is a single binary. It gives your agent a real public IPv6 address inside your organisation's /64, an MCP endpoint on localhost, and an egress proxy that sends your traffic out from that identity.

It runs as a normal user — no root, no kernel modules, no sudo for anything but copying the binary into place.

Get your API key

Sign up at route6.me (opens in a new tab), verify your email, then copy your agent's key from the dashboard (opens in a new tab) under Agents. It starts with sk_a6_.

Install

curl -fsSL https://dl.route6.me/install.sh | sh
Route6 client v1.1.0
  platform:  linux/amd64
  source:    https://dl.route6.me/v1.1.0/r6me_1.1.0_linux_amd64.tar.gz
  install:   /usr/local/bin/r6me

downloading...
  checksum verified
  installed /usr/local/bin/r6me

r6me v1.1.0

The script works out your platform, downloads the matching build and its published checksum, and refuses to install if the two disagree. As root it installs to /usr/local/bin; as a normal user it installs to ~/.local/bin and tells you if that is not on your PATH.

Piping to a shell is worth a second's thought, so here is what this one does. It fetches over HTTPS from our own host, verifies a sha256 against the manifest published beside the binary, and aborts on any mismatch. If you would rather read it first:

curl -fsSL https://dl.route6.me/install.sh -o install.sh
less install.sh
sh install.sh

sh install.sh --dry-run prints exactly what it would do and changes nothing.

Useful flags: --version vX.Y.Z to pin, --dir <path> to choose where the binary lands, --no-systemd to skip the service unit, --key sk_a6_… to write the config in the same step.

Already in a node or python project? Install from the registry you are already using — these are launchers around the same binary, verified the same way, and they are the route for Windows, where there is no curl | sh:

npm install -g @route6/agent    # or: pip install route6
route6 up

Both give you the same route6 command. They fetch the binary on first use rather than at install time, so npm install --ignore-scripts and pip's lack of a post-install hook both behave identically.

⚠️

Prefer to do it by hand? Every release ships a checksums.txt, and the client is the executable that will hold your network identity — so verify it either way:

curl -fsSLO https://dl.route6.me/v1.1.0/r6me_1.1.0_linux_amd64.tar.gz
curl -fsSLO https://dl.route6.me/v1.1.0/checksums.txt
sha256sum --check --ignore-missing checksums.txt   # macOS: shasum -a 256
tar xzf r6me_1.1.0_linux_amd64.tar.gz
sudo install -m 0755 r6me /usr/local/bin/r6me

--ignore-missing checks the one file you downloaded instead of failing on the other eleven builds in the manifest. Browse dl.route6.me (opens in a new tab) for the full matrix: linux, macOS and windows on amd64/arm64, plus arm32, mips and riscv64 for routers and embedded boards.

Configure

The daemon reads ~/.r6me/config.toml. If you already have your key, the installer can write it for you in the same step — useful when an agent is installing itself and nobody is at a keyboard:

curl -fsSL https://dl.route6.me/install.sh | ROUTE6_API_KEY=sk_a6_your_key_here sh

Otherwise write it yourself:

mkdir -p ~/.r6me && chmod 700 ~/.r6me
cat > ~/.r6me/config.toml <<'EOF'
api_key = "sk_a6_your_key_here"
EOF
chmod 600 ~/.r6me/config.toml

api_key is the only required field. ROUTE6_API_KEY in the environment works instead, if you would rather not write the key to disk.

Optional keys: gateway (defaults to https://gw.route6.me), agent_id (pins this install to one agent — leave it out and the daemon adopts the identity the fabric hands it on first connect), and loopback_only = true (refuse any inbound forward whose target is not loopback).

Connect

r6me up
r6me status
r6me daemon started (pid 3434539); logs: /root/.r6me/daemon.log

r6me up backgrounds itself and writes to ~/.r6me/daemon.log. status prints the live state as JSON:

{
  "transport": "connected",
  "generation": 207,
  "wg_health": "up",
  "mesh_transport": "wg",
  "forwards": [],
  "mcp": "127.0.0.1:3000/mcp",
  "agent_id": 39,
  "client_version": "v1.1.0",
  "advisory": "ok",
  "gateway": "https://gw.route6.me"
}

You want "transport": "connected". wg_health reaches up a few seconds later, once the mesh tunnel finishes its handshake.

Point your agent at it

The daemon serves MCP on http://localhost:3000/mcp, with no API key in the client config — the daemon already holds it.

{
  "mcpServers": {
    "route6": { "url": "http://localhost:3000/mcp" }
  }
}

That works for Claude Desktop, Cursor, Cline and anything else that speaks MCP over HTTP. See MCP Integration for per-client config.

Send traffic from your agent's IP

The daemon also runs a local proxy on port 1080 speaking SOCKS5, HTTP CONNECT and plain HTTP. Anything pointed at it leaves from your agent's own public address:

export http_proxy=http://127.0.0.1:1080
export https_proxy=http://127.0.0.1:1080
curl https://api64.ipify.org
2001:67c:3f4:801f::1

IPv4-only destinations work too — the fabric reaches them over NAT64, so an IPv4 site sees you arrive from a Route6 address without your machine needing IPv4 at all.

⚠️

Use lowercase http_proxy with curl. libcurl deliberately ignores an uppercase HTTP_PROXY (it could arrive from a CGI Proxy: header) while honouring HTTPS_PROXY either way — so uppercase-only looks like it works, tunnels https fine, and sends plain http straight out your normal connection instead. Most other clients (python requests, node, Go) accept either case.

For tools that speak SOCKS: ALL_PROXY=socks5h://127.0.0.1:1080.

The commands

CommandWhat it does
r6me upconnect and background the daemon
r6me downdisconnect
r6me statustransport state, config generation, forwards, MCP endpoint
r6me ssh <name>open a shell on a team-mate over the private mesh (Team plans)
r6me versionprint the version

r6me up takes --fg to stay in the foreground (use this under systemd or in a container), and --loopback-only to refuse inbound forwards that target anything but loopback.

Keeping it running

r6me up backgrounds itself and survives your shell closing — but nothing brings it back after a reboot, and nothing restarts it if the process dies. A supervisor fixes both.

If you installed as root on a systemd host, the installer already wrote the unit — it just does not enable it, so the decision stays yours:

sudo systemctl enable --now r6me

There is a third difference worth knowing: under systemd the daemon runs with --fg and its output goes to journald, which rotates. The bare r6me up path writes ~/.r6me/daemon.log, and nothing rotates that today.

The unit the installer writes, for reference (pass --no-systemd to skip it):

[Unit]
Description=Route6 agent
After=network-online.target
Wants=network-online.target
 
[Service]
ExecStart=/usr/local/bin/r6me up --fg
Restart=always
User=root
Environment=HOME=/root
 
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now r6me
⚠️

User and HOME must agree. The daemon looks for config.toml and its identity under $HOME/.r6me, so if you run as a dedicated user, set HOME to that user's home and put the config there — otherwise the daemon starts, finds no key, and exits. Set R6ME_STATE_DIR instead if you want the state somewhere else entirely.

The daemon keeps its WireGuard key and applied configuration in that directory, so a restart resumes the same identity rather than registering a new one. Verified: agent_id and generation were unchanged across a systemctl restart.

Upgrading

Re-run the installer. It is idempotent, and it replaces the binary by writing a new file and moving it into place rather than overwriting the running one:

r6me down
curl -fsSL https://dl.route6.me/install.sh | sh
r6me up          # or: sudo systemctl restart r6me

Your identity survives because it lives in ~/.r6me, not in the binary — agent_id and generation are unchanged across the upgrade. status reports an advisory field — ok, update_available, or unsupported — so you can tell whether the version you are on is still current without checking the site.

To pin a specific version, or to roll back:

curl -fsSL https://dl.route6.me/install.sh | sh -s -- --version v1.1.0

Troubleshooting

no api key (set ROUTE6_API_KEY or api_key in …/config.toml) — the daemon found no key. Check ~/.r6me/config.toml exists and the key is quoted.

/v2/session status 401 in ~/.r6me/daemon.log — the key is wrong or was revoked. Copy it again from the dashboard.

transport: connected but wg_health stays connecting — the control channel is fine and the mesh tunnel is not. Egress and MCP still work; only the private team mesh is affected. Check ~/.r6me/daemon.log for wg-pubkey.

bind: address already in use — something else holds 3000, 3001 or 1080. Those ports are fixed; stop the other process, or run this agent on another machine.

x509: certificate signed by unknown authority — you are on a client older than v1.2.2, on a machine that ships no CA certificates at all. Nothing is wrong with our certificate (it is an ordinary Let's Encrypt one); that machine simply has nothing to verify any certificate against.

From v1.2.2 the client carries its own copy of the Mozilla root bundle, used only when the machine has no trust store of its own — so this cannot happen on a current client. Upgrade, or install the certificates:

apt-get install -y ca-certificates    # Debian / Ubuntu
apk add ca-certificates               # Alpine

Measured, in case you are wondering whether your base image is affected: node:20-bookworm-slim and debian:stable-slim ship zero roots; python:3.12-slim has 150, alpine:latest and python:3.12-alpine 119, and node:20-alpine 145. Only the Debian-based slim images were ever affected.

If your machine does have a trust store and you still see this, it is not a missing bundle — look at a TLS-inspecting proxy on the network, or a system clock wrong enough that every certificate reads as expired. The client's own trust store is never used when yours exists, so your administrator's choices still decide.

Everything, in one place~/.r6me/daemon.log is the whole story. Read it before anything else.

Running in a container

Docker needs a little more setup, because inbound forwards have to be told which machine answers them. See Container (Docker).