---
title: "Expose Autohand Code with Cloudflare Tunnel"
source: https://docs.autohand.ai/tutorials/expose-autohand-with-cloudflare-tunnel
---

# Expose Autohand Code with Cloudflare Tunnel

Run Autohand Code on your VPS or Docker host, then expose SSH or an HTTP job trigger through an outbound Cloudflare Tunnel.

Intermediate 30 min

## Provider docs used

Cloudflare Tunnel lets a connector make outbound-only connections from your server to Cloudflare. This tutorial follows Cloudflare's current Tunnel setup flow and adapts it for an Autohand Code runner: [Create a tunnel](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/get-started/create-remote-tunnel/).

## Architecture

Autohand Code still runs on a Linux host or Docker host. Cloudflare Tunnel only handles remote access. The safest default is a local runner process bound to `127.0.0.1`, with Cloudflare Access or a signed webhook in front of the public hostname.

| Expose | Use when |
|---|---|
| HTTP webhook | GitHub, Linear, Slack, or a Worker needs to trigger a job |
| SSH | Humans need private terminal access through Cloudflare Access |
| Local dashboard | You build a small internal UI for queued Autohand Code jobs |

## Step 1: Run a local Autohand Code job trigger

Keep the process bound to localhost. The tunnel will publish it later.

``` js
// server.mjs
import { createServer } from "node:http";
import { spawn } from "node:child_process";

const repoDir = process.env.AUTOHAND_REPO_DIR;
const token = process.env.AUTOHAND_RUNNER_TOKEN;

createServer((req, res) => {
  if (req.method !== "POST" || req.headers.authorization !== `Bearer ${token}`) {
    res.writeHead(401);
    res.end("unauthorized");
    return;
  }

  const job = spawn("autohand", [
    "-p",
    "Review the latest repository state and summarize action items",
    "--restricted",
    "--output-format",
    "stream-json"
  ], { cwd: repoDir, env: process.env });

  job.stdout.on("data", chunk => process.stdout.write(chunk));
  job.stderr.on("data", chunk => process.stderr.write(chunk));
  res.writeHead(202);
  res.end("queued\n");
}).listen(8787, "127.0.0.1");
```

## Step 2: Create the tunnel

You can create the tunnel in the Cloudflare dashboard or with `cloudflared`. The dashboard flow gives you an install command for your connector host.

``` bash
cloudflared tunnel login
cloudflared tunnel create autohand-runner
cloudflared tunnel route dns autohand-runner autohand-runner.example.com
```

## Step 3: Route the hostname to localhost

``` yaml
# ~/.cloudflared/config.yml
tunnel: autohand-runner
credentials-file: /home/autohand/.cloudflared/autohand-runner.json

ingress:
  - hostname: autohand-runner.example.com
    service: http://127.0.0.1:8787
  - service: http_status:404
```

``` bash
cloudflared tunnel run autohand-runner
```

## Step 4: Protect the route

For human access, put Cloudflare Access in front of the hostname. For machine access, validate a bearer token or webhook signature in the local runner before starting Autohand Code.

``` bash
export AUTOHAND_RUNNER_TOKEN="$(openssl rand -hex 32)"
export AUTOHAND_REPO_DIR="$HOME/work/your-repo"
source ~/.autohand/env
node server.mjs
```

## Step 5: Trigger a job

``` bash
curl -X POST https://autohand-runner.example.com \
  -H "Authorization: Bearer $AUTOHAND_RUNNER_TOKEN"
```

## Operations checklist

-   Keep the origin listener bound to `127.0.0.1`.
-   Use Cloudflare Access for human-triggered jobs.
-   Use request signing or bearer tokens for machine-triggered jobs.
-   Run `cloudflared` under systemd so the tunnel restarts after reboot.
-   Log both tunnel health and Autohand Code job output.