Skip to main content

Quickstart - Tunnel

The Acurast reverse tunnel exposes a service running inside a deployment to the public internet, without the processor needing an inbound public IP or open ports. The processor only makes outbound connections to a relay node, the relay then routes incoming traffic from external users back to the processor.

Once the tunnel is up, the deployment is reachable at https://<clientId>.acu.run with a valid Let's Encrypt certificate provisioned automatically.

Optionally, it is possible to use a different domain instead of the acu.run suffix when properly configured.

Prerequisites

An Android Core processor

Tunnel deployments require Processor version 1.26.0.

Full guide: Become a Compute Provider.

Acurast CLI

Install the CLI globally:

npm install -g @acurast/cli

ACU balance

Tunnel deployments require ACU / cACU to cover execution costs. On Canary, claim free cACU from the faucet ↗.

(Optional) A DNS suffix you control

It is possible to use the tunnel with a custom domain if proof of ownership is provided with a specific TXT DNS record described in Step 2.


Step 1 - Create a project

npx @acurast/cli new my-tunnel-app
cd my-tunnel-app
acurast init

This creates acurast.json (deployment config) and .env (secrets). See the CLI docs for all available commands and options.


(Optional) Step 2 - Configure DNS

In case of using a custom domain instead of acu.run, pick an owned subdomain (tunnel.example.com in the examples below) and add records at your DNS provider.

The domain needs to point to one or more relay nodes:

DomainIP
relay-1.mainnet.acurast.com82.220.91.110

Record 1 - wildcard pointing at the relays

The public URL of every deployment is https://<clientId>.<yourDomainSuffix>. The wildcard makes any <clientId> resolve to the relays:

*.tunnel.example.com.   A   82.220.91.110
*.tunnel.example.com. A ...
*.tunnel.example.com. A ...

Record 2 - TXT record proving deployer ownership

The relay validates that whoever runs a deployment under your suffix also controls the suffix's DNS. Add a TXT record at _acu.<yourDomainSuffix>:

_acu.tunnel.example.com.   TXT   "<base64-sha256 hash>"

The value is base64(sha256(<deployer-pubkey-bytes> || <yourDomainSuffix>)), where <deployer-pubkey-bytes> is the 32-byte public-key bytes of the Acurast account that submits the deployment.

Use ss58.org ↗ to convert your deployer SS58 address to its public-key hex, then compute the TXT value:

{ printf '%s' MY_ADDRESS_HEX_VALUE | xxd -r -p; printf '%s' MY_DOMAIN_SUFFIX; } | openssl dgst -sha256 -binary | base64

Replace MY_ADDRESS_HEX_VALUE with your deployer address's hex value and MY_DOMAIN_SUFFIX with your subdomain (e.g. tunnel.example.com).

If multiple deployer accounts share the same suffix, publish multiple TXT records on the same name, the relay accepts any matching record.


Step 3 - Write your app

Your app opens the tunnel by calling tunnel.start(spec) with the relay addresses, your domain suffix, and the local port your service listens on. Once it returns { url, clientId, ... }, the tunnel is live and external traffic is forwarded to your local port.

The Cargo runtime exposes the tunnel via a JSON-RPC bridge on the abstract Unix socket named in $BRIDGE_SOCKET. The example below opens the tunnel from Python and points it at whatever local service you choose to run alongside it.

info

Privileged ports (< 1024) cannot be bound inside the PRoot sandbox. Use ports >= 1024 for your local service. The public-facing port is always 443.

app/start.sh - installs Python and runs the tunnel script:

app/start.sh
#!/bin/sh

apt-get update
apt-get install -y python3 python3-cryptography

python3 "$(dirname "$0")/tunnel.py"

app/tunnel.py - generates a P-256 identity key and opens the tunnel via the bridge socket:

app/tunnel.py
import base64
import json
import os
import socket
import time

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec

DOMAIN_SUFFIX = "acu.run"
TUNNEL_RELAYS = [
"relay-1.mainnet.acurast.com:4433",
]
LOCAL_ADDR = "127.0.0.1:8080" # point at your local service

BRIDGE_SOCKET = os.environ["BRIDGE_SOCKET"]


def rpc(method, params):
req = {"jsonrpc": "2.0", "method": method, "params": params, "id": 1}
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect("\0" + BRIDGE_SOCKET)
s.sendall((json.dumps(req) + "\n").encode())
line = s.makefile("rb").readline()
s.close()
resp = json.loads(line)
if "error" in resp:
raise RuntimeError(resp["error"])
return resp["result"]


def primary_key_b64():
"""Generate a P-256 keypair as base64-encoded PKCS#8 DER.

Persist the bytes to disk and reload on subsequent starts to keep the
same `clientId` (and skip re-running ACME).
"""
key = ec.generate_private_key(ec.SECP256R1())
pkcs8 = key.private_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
return base64.b64encode(pkcs8).decode("ascii")


info = rpc("tunnel_start", [{
"serverAddrs": TUNNEL_RELAYS,
"domainSuffix": DOMAIN_SUFFIX,
"localAddr": LOCAL_ADDR,
"primaryKey": {"algorithm": "Secp256r1", "bytes": primary_key_b64()},
"acmeStaging": False,
}])
print(f"Tunnel ready at: {info['url']}")

# Keep the process alive while the tunnel is up.
while True:
time.sleep(30)

For an end-to-end reference (dropbear SSH + callback events + cert reuse) see acurast-tunnel-proot ↗.

Full tunnel API reference: Cargo Runtime Environment - Tunnel.

note

The TUNNEL_RELAYS list needs to correspond to the DNS records configured on Step 2


Step 4 - Configure acurast.json

acurast.json
{
"projects": {
"cargo-tunnel": {
"projectName": "cargo-tunnel",
"fileUrl": "app",
"entrypoint": "start.sh",
"runtime": "Shell",
"image": {
"url": "https://github.com/termux/proot-distro/releases/download/v4.30.1/ubuntu-questing-aarch64-pd-v4.30.1.tar.xz",
"sha256": "5ab35b90cd9a9f180656261ba400a135c4c01c2da4b74522118342f985c2d328"
},
"network": "mainnet",
"requiredModules": ["Shell"],
"minProcessorVersions": {
"android": "1.26.0"
},
...
}
}
}

For all other fields, see the CLI configuration reference.


Step 5 - Deploy

acurast deploy

The CLI uploads your app to IPFS and registers the deployment on-chain. The processor pulls it down, starts your app, and the app opens the tunnel.

Monitor your deployment:

acurast deployments ls

Step 6 - Connect

Once the deployment is up and running, connect from anywhere.

For HTTP/HTTPS services:

curl https://<clientId>.acu.run

The tunnel does TLS pass-through, so any protocol you can wrap in TLS works. For raw-TCP services like SSH, prefix with openssl s_client as a ProxyCommand:

ssh -o ProxyCommand='openssl s_client -quiet \
-servername <clientId>.acu.run \
-connect <clientId>.acu.run:443' \
root@<clientId>

Next steps