Skip to main content

Quickstart - Tunnel (Canary Preview)

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>.<yourDomainSuffix>:8443 with a valid Let's Encrypt certificate provisioned automatically.

Prerequisites

A Canary Android Core processor

Tunnel deployments require Processor version 1.26.0-rc1 on Canary.

Full guide: Become a Compute Provider.

Acurast CLI

Install the CLI globally:

npm install -g @acurast/cli

ACU balance

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

A DNS suffix you control

The tunnel relay only accepts deployments under a domain suffix whose DNS records prove that you, the deployer, own the suffix. Pick any subdomain you control, tunnel.example.com, apps.mybrand.com, etc. You will configure two records under it 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.


Step 2 - Configure DNS

Pick a subdomain you control (tunnel.example.com in the examples below) and add two records at your DNS provider.

Record 1 - wildcard pointing at the relay

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

*.tunnel.example.com.   CNAME   relay-2.canary.acurast.com.
*.tunnel.example.com. CNAME ...

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 Node.js runtime exposes the tunnel via _STD_.tunnel.* global callbacks. The example below starts a small HTTP server on 127.0.0.1:3000 and asks the tunnel to forward https://<clientId>.tunnel.example.com:8443 to it.

index.js
const http = require('http');

const DOMAIN_SUFFIX = 'tunnel.example.com';
const TUNNEL_RELAYS = ['relay-2.canary.acurast.com:4433'];
const LOCAL_ADDR = '127.0.0.1:3000';

// Generate or load a P-256 identity key as base64-encoded PKCS#8 DER.
// Persist this value across runs to keep the same `clientId` (and avoid
// re-running ACME on every restart).
const primaryKeyBase64 = '...';

const spec = {
serverAddrs: TUNNEL_RELAYS,
domainSuffix: DOMAIN_SUFFIX,
localAddr: LOCAL_ADDR,
primaryKey: {
algorithm: 'Secp256r1',
bytes: primaryKeyBase64,
},
acmeStaging: false,
};

http.createServer((_req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello from Acurast\n');
}).listen(3000, '127.0.0.1', () => {
print('HTTP server listening on 127.0.0.1:3000');

_STD_.tunnel.start(
spec,
(info) => print('Tunnel ready at: ' + info.url),
(err) => print('Tunnel failed: ' + err),
);
});

For an end-to-end reference with multi-processor leader election and certificate persistence, see acurast-tunnel-scripts ↗.

Full tunnel API reference: Node.js Runtime Environment - Tunnel.


Step 4 - Configure acurast.json

acurast.json
{
"projects": {
"my-tunnel-app": {
"projectName": "my-tunnel-app",
"fileUrl": "index.js",
"entrypoint": "index.js",
"runtime": "NodeJS",
"network": "canary",
"minProcessorVersions": {
"android": "122"
},
...
}
}
}

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 logs print Tunnel ready at: https://<clientId>.tunnel.example.com:8443, connect from anywhere.

For HTTP/HTTPS services:

curl https://<clientId>.tunnel.example.com:8443/

Add -k while testing on Canary (the certificate is issued from Let's Encrypt staging, which is not in the default trust store). Mainnet uses Let's Encrypt production.

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>.tunnel.example.com \
-connect <clientId>.tunnel.example.com:8443' \
root@<clientId>

Next steps