> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getpostern.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up remote access

> Open two ports to your own devices, and one port to the internet only if claude.ai or ChatGPT needs it.

<Info>
  **Before you start**

  * **A Tailscale account, and admin access to Tailscale's admin console.** The free Personal plan covers a household. Sign up at [tailscale.com](https://tailscale.com).
  * **Postern installed**, and the Console open in your browser. Step 1 opens it if Postern runs on another computer.
  * **About 20 minutes, once.** Postern takes ports 8443 and 10000 on your tailnet. Port 443 goes to the internet only if you do step 4.
  * **Your password manager open.** In step 6, Postern shows you an agent key one time only.
  * **A Mac or a Linux computer.** Postern runs under Docker, so Windows should work. Nobody has run this page there. In PowerShell, `curl` means something else, so type `curl.exe` in the checks below.
</Info>

Your tailnet is the private network Tailscale builds between the devices signed
into your account. Nothing else can reach it.

Not every step here is for everyone. Read this first, then do the steps that
apply to you, in order.

| Steps       | Who does them                                                                         |
| ----------- | ------------------------------------------------------------------------------------- |
| **1**       | Only you, if Postern runs on a computer you are not sitting at.                       |
| **2 and 3** | Anyone who wants their own devices to reach Postern. **Most people stop here.**       |
| **4 to 6**  | Only if claude.ai or ChatGPT must reach Postern. These open one port to the internet. |

None of this is on until you turn it on. [Turn the public port
off](#turn-the-public-port-off) shuts it again whenever you want.

<Steps>
  <Step title="Open the Console over SSH, if Postern runs on another computer" titleSize="h2" id="ssh-tunnel">
    Skip this if you sit at the machine Postern runs on. The Console only answers on
    the machine it runs on. Run this on your own computer, not on the machine Postern
    runs on:

    ```bash theme={"system"}
    ssh -N -L 127.0.0.1:8787:127.0.0.1:8787 YOUR_MACHINE
    ```

    Replace `YOUR_MACHINE` with however you reach that computer: a name, a
    `user@host`, or an IP address. The terminal prints nothing and does not return to
    a prompt. That is the command at work, not a hang. Leave that window open, then
    open [http://localhost:8787](http://localhost:8787). Press **Control-C** when you
    want to stop. [Open the Console](/start/install#open-console) carries the long
    version.

    A tunnel does not move the Console off that machine:
    [port 8787 has no password, and its address stands in for one](/reference/ports#the-console-has-no-authentication).
  </Step>

  <Step title="Install Tailscale and turn on two switches" titleSize="h2" id="tailscale">
    Install Tailscale on the machine Postern runs on, and on every device you want
    to reach it from ([tailscale.com/download](https://tailscale.com/download)).
    Sign them all into the same Tailscale account.

    On the machine Postern runs on, run `tailscale up`, then follow what it prints
    to sign that machine in.

    <Note>
      On a Mac, the App Store and desktop builds leave `tailscale` off your PATH, so
      `which tailscale` returns nothing. It sits inside the app, with a capital T.
      Run this once, in the same terminal:

      ```bash theme={"system"}
      export TS=/Applications/Tailscale.app/Contents/MacOS/Tailscale
      ```

      Then put `$TS` in front of every `tailscale` command below. Close that
      terminal and `$TS` is gone. Linux packages put `tailscale` on your PATH.
    </Note>

    Two settings in Tailscale's admin console decide whether any address below ever
    answers. Turn them on in this order.

    <Warning>
      HTTPS certificates put this machine's name on the Certificate Transparency
      log: a public list of every secure-website certificate ever issued. Anyone can
      read it, and entries cannot be removed. Rename the machine to something dull
      first, and never treat its name as protection.
    </Warning>

    Open [login.tailscale.com/admin/dns](https://login.tailscale.com/admin/dns).
    Turn on MagicDNS, which lets your devices reach each other by name. Then turn on
    HTTPS certificates. Tailscale's own instructions walk both, in that order:
    [Enabling HTTPS](https://tailscale.com/kb/1153/enabling-https). Both must be on.
    If either is off, the failure is silent: the port never answers, and no log
    names the cause.

    Get this machine's Tailscale name from `tailscale status` on that machine.
    Tailscale's admin console lists it too, on the page its documentation calls
    Machines. The name looks like `gateway.example-tailnet.ts.net`, and every
    command below needs it.
  </Step>

  <Step title="Open two ports to your own devices" titleSize="h2" id="your-devices">
    <Warning>
      Never open port 8787, the Console port, whole to any network. Anything that
      reaches it holds full control of Postern, and no password stands behind it.
      The first command below opens one address on that port and nothing else.
      [Reachability is the authentication](/reference/ports#the-console-has-no-authentication).
    </Warning>

    Run both on the machine Postern runs on:

    ```bash theme={"system"}
    tailscale serve --bg --https=8443 --set-path=/api/ingest http://127.0.0.1:8787/api/ingest
    tailscale serve --bg --https=10000 http://127.0.0.1:8788
    ```

    `--bg` keeps Tailscale in the background and gives your prompt back. Without it
    the terminal is stuck. Both commands work on Tailscale 1.98.9, and `--set-path`
    is current syntax there.

    Your own devices now reach two addresses, and the internet reaches neither:

    * `https://gateway.example-tailnet.ts.net:8443/api/ingest/<connection-id>` — your iPhone pushes Apple Health data here.
    * `https://gateway.example-tailnet.ts.net:10000/mcp` — an agent on your own devices connects here, with an agent key.

    Check it from a second computer on your tailnet. It needs Tailscale, the same
    account, and a terminal, so a phone will not do. Substitute your own machine
    name. This prints only the three-digit answer code:

    ```bash theme={"system"}
    curl -so /dev/null -w '%{http_code}' https://gateway.example-tailnet.ts.net:8443/api/agents
    ```

    It must print **404** — nothing is there, because the first command opened
    `/api/ingest` alone. **200** means the whole port is open: stop, and run both
    commands again exactly. Nothing at all means MagicDNS or HTTPS certificates is
    off ([step 2](#tailscale)).

    The second check needs a connection id, and none exists until you press
    **Create your ingest endpoint** on the
    [Apple Health](/connect/apple-health#create-endpoint) card. Then this must print
    **401** — the address answers, and it wants its secret:

    ```bash theme={"system"}
    curl -so /dev/null -w '%{http_code}' https://gateway.example-tailnet.ts.net:8443/api/ingest/<connection-id>
    ```
  </Step>

  <Step title="Open one port to the internet for a hosted agent" titleSize="h2" id="publish">
    Do this only for a hosted agent: an AI app that runs on a company's servers, like
    claude.ai or ChatGPT. It can never reach your home network on its own, and it
    connects out on port 443 only. Open 443, pointed at port 8788, the agent port.
    Never point it at 8787.

    Your tailnet policy must let this machine run a funnel. Tailscale keeps that
    policy in its admin console, on the page its documentation calls Access
    controls. Tailscale's own instructions cover the funnel permission:
    [Tailscale Funnel](https://tailscale.com/kb/1223/funnel). Scope the permission to
    this machine's own tag, never to the whole tailnet. `serve` and `funnel` are
    last-command-wins per port. A mistyped command on any machine that holds the
    permission can open something to the internet
    ([the whole argument](/reference/ports#reaching-postern-from-another-network)).
    This page does not walk Tailscale's policy file. If the permission is not there,
    the command below refuses out loud and prints a link that turns it on. Open that
    link, then run the command again.

    <Warning>
      Run the command below once, and get it right first time. Repeated `serve` and
      `funnel` commands re-issue certificates, and the free certificate service
      limits how often it will issue one for a name. Past that limit you lose your
      own hostname for hours. Retry in [step 5](#probe-outside), never here.
    </Warning>

    ```bash theme={"system"}
    tailscale funnel --bg --https=443 http://127.0.0.1:8788
    tailscale funnel status
    ```

    In `tailscale funnel status`, the 443 line must read `(Funnel on)`, and every
    other line `(tailnet only)`.

    Now tell Postern its address. In the Console, click **Settings** in the left
    sidebar, then find **Remote access**. In the row headed **Public · the address
    this gate answers on**, press **Set the address…**.

    A field labelled **Your gate’s public address** appears, with the placeholder
    `https://your-mac.tailnet.ts.net`. Type `https://` and this machine's Tailscale
    name, with nothing after it — not the `/mcp` address you hand an agent. Postern
    builds that one itself, and it refuses an address that carries a path.

    Postern does not save the address until you confirm it. Press **Publish this
    gate** on the bar headed **Publish this gate to the public internet?**. That bar
    lists what it costs you. The row then reads **restart to apply**, and a message
    reads `Saved — restart the gateway to start publishing.`

    <Frame caption="Settings → Remote access. The Public row reads off until you set an address. Recorded on a gateway whose Plaid source was in Sandbox, so ignore any Plaid detail in this shot.">
      <img src="https://mintcdn.com/postern/E8R3wfogdGSukifu/images/remote-console-card.png?fit=max&auto=format&n=E8R3wfogdGSukifu&q=85&s=7a39a80ce863d524cd6f2f5076115a53" alt="Settings → Remote access: the Public row reads off, with a Set the address button." width="1440" height="1355" data-path="images/remote-console-card.png" />
    </Frame>

    Restart Postern from the folder that holds `docker-compose.yml` — the `postern`
    folder you made in [Install Postern](/start/install):

    ```bash theme={"system"}
    cd postern
    docker compose restart app
    ```

    The restart is not optional: Postern reads the address once, at start-up. The
    **Public** row then reads **published**, and the **OAuth bridge** row reads
    **On — published at** your address. That bridge is on exactly when Postern has
    an address, and off otherwise. There is no separate switch.
  </Step>

  <Step title="Check it from a device that is not on your tailnet" titleSize="h2" id="probe-outside">
    A device on your tailnet cannot prove this: from inside, a funnel and a private
    serve look identical. Use a device off your tailnet that can run these commands
    — a laptop on a phone's cellular hotspot does it. Substitute your own machine
    name. All three must print **200**, **401** and **404**, in that order:

    ```bash theme={"system"}
    curl -so /dev/null -w '%{http_code}' https://gateway.example-tailnet.ts.net/.well-known/oauth-protected-resource/mcp
    curl -so /dev/null -w '%{http_code}' https://gateway.example-tailnet.ts.net/mcp
    curl -so /dev/null -w '%{http_code}' https://gateway.example-tailnet.ts.net/api/agents
    ```

    200 means it answered. 401 means it answered and wants a key. 404 means nothing
    is there. On the third line that is the answer you want: the Console port did
    not come with it. Touch no agent app until all three read correctly.

    <Note>
      claude.ai remembers what it found here for about five minutes, failures
      included. Change one thing, wait five minutes, then try once.
    </Note>
  </Step>

  <Step title="Connect claude.ai or ChatGPT" titleSize="h2" id="connect-agent">
    First create the key. In the Console, click **Agents & keys** in the left
    sidebar, then click **Mint a key**. Three fields:

    * **Name** — what you will call this agent.
    * **Grant which sectors** — six areas of your life: finance, mail, calendar, contacts, health, home. The form says `Nothing is granted until you pick it.`
    * **Expires** — four chips: **Never**, **30 days**, **60 days** and **90 days**. Once you have set a public address, the form starts on **90 days**.

    [Mint your first key](/start/first-key) walks the whole form.

    <Warning>
      Pick anything but **Never**. Postern refuses a key with no expiry at the hosted
      sign-in page: `This agent key never expires. Keys used with a cloud client must
              have an expiry — mint or rotate a key with a 30/90/365-day expiry in the
              Console, then try again.` The Console has no 365-day chip, and **90 days** is
      its longest. **Never** dead-ends you at the last step of this page.
    </Warning>

    Press **Mint the key**. A screen headed **Agent key — shown once** appears, with
    a **Copy** button. Copy the key into your password manager now. Postern cannot
    show you this key again. It keeps a scrambled copy it can check against, never
    the key itself. Lose it and you create another.

    <Warning>
      The next **Copy** button replaces whatever is on your clipboard — the agent key
      too. Store the key first.
    </Warning>

    The address you hand the agent is your published address plus `/mcp`. The
    Console prints the exact value under **Agents & keys** → **Set up the bridge →**,
    with its own **Copy** button.

    In claude.ai, open its settings and find its connectors. Add a connector of your
    own. Paste the address into it. If it asks for a client id or a client secret,
    leave both empty. Postern issues neither.

    claude.ai then sends you to a page your own Postern serves, headed **Authorize
    agent access**. Paste the agent key under **Paste an agent key from the Console**.
    Press **Approve**. claude.ai holds a token that rotates from then on. ChatGPT
    takes a longer path: [Connect ChatGPT](/connect/chatgpt).

    [Revoke a key and every token made from it dies with it](/reference/agent-keys#revoking-rotating-and-removing).
    Postern denies the agent's next call.
  </Step>
</Steps>

## Turn the public port off

This is not a step. It undoes [Open one port to the internet](#publish), whenever
you want that port shut again. Nothing here touches your own devices.

Three acts. Do all three. Any two leave you half-off — a funnel with no address,
an address with no funnel, or Postern still on the old one.

On the machine Postern runs on:

```bash theme={"system"}
tailscale funnel --https=443 off
```

In the Console, click **Settings**, then find **Remote access**. In the
**Public** row, press **Stop publishing…**, then press **Stop publishing** on
the bar headed **Stop publishing this gate?**. A message reads
`Stopped — it stays reachable until the gateway restarts.`

<Note>
  If `.env` on the machine Postern runs on holds an `MCP_PUBLIC_URL` line, the
  Console refuses, and says so: `The box publishes this gateway: MCP_PUBLIC_URL
      is set in its environment, and the box wins over anything saved here.` Delete
  that line from `.env` first. `.env` is the settings file beside
  `docker-compose.yml`.
</Note>

Then restart:

```bash theme={"system"}
cd postern
docker compose restart app
```

Confirm no funnel remains. The output must contain no `AllowFunnel` anywhere:

```bash theme={"system"}
tailscale serve status --json
```

Nothing about [Open two ports to your own devices](#your-devices) changes. Your own
devices still reach Postern.

## Confirm it works

* Tailscale's admin console → DNS: MagicDNS on, HTTPS certificates on.
* From a second computer on your tailnet, `/api/agents` on port 8443 prints **404**.
* `tailscale funnel status`: the 443 line reads `(Funnel on)`, every other line `(tailnet only)`.
* Console → Settings → Remote access: the **Public** row reads **published**, not **restart to apply**.
* Console → Settings → Remote access: the **OAuth bridge** row reads **On — published at** your address.
* From a device off your tailnet, the three checks in [step 5](#probe-outside) print **200**, **401** and **404**.
* Console → **Agents & keys** lists your key, and its sub-line reads `expires in 30d`, `expires in 60d` or `expires in 90d` — never `never expires`.

## If something went wrong

| What you see                                                           | What to do                                                                                                                                     |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| The port never answers, and nothing in any log says why                | MagicDNS or HTTPS certificates is off. Turn both on at [login.tailscale.com/admin/dns](https://login.tailscale.com/admin/dns).                 |
| `which tailscale` returns nothing, on a Mac                            | Run `export TS=/Applications/Tailscale.app/Contents/MacOS/Tailscale`, then put `$TS` in front of every `tailscale` command.                    |
| `curl` prints a web page instead of three digits, on Windows           | In PowerShell, `curl` means something else. Type `curl.exe` instead.                                                                           |
| `/api/agents` answers 200 from your tailnet                            | Port 8787 is open whole. Run both commands in [step 3](#your-devices) again, exactly as written.                                               |
| The **Public** row still reads **off** after you typed the address     | Postern saves the address only when you press **Publish this gate** on the confirmation bar ([step 4](#publish)).                              |
| The **Public** row reads **restart to apply**                          | Postern reads the address only at start-up. Run `docker compose restart app` from the folder that holds `docker-compose.yml`.                  |
| A hosted agent says it could not reach you, and your logs show nothing | The funnel is on a port other than 443, so the request never reaches you. Run `tailscale funnel status`; the 443 line must read `(Funnel on)`. |

## What you have now

| If you stopped after     | What can reach Postern                                                                                                                                                                                                                                   |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [step 1](#ssh-tunnel)    | Nothing is open. The Console reaches your computer through the SSH tunnel, and nowhere else.                                                                                                                                                             |
| [step 3](#your-devices)  | Your own devices reach two addresses on your tailnet. The internet reaches nothing.                                                                                                                                                                      |
| [step 6](#connect-agent) | The internet reaches eight addresses on one port. Seven are the sign-in machinery and hold no data of yours; the eighth is `/mcp`, which needs a key. The Console is not among them. [The full list](/reference/oauth-bridge#what-the-internet-reaches). |

Three costs stay with you:

* a public hostname in a permanent certificate log
* a Tailscale account to keep
* an address that stays live until you [turn it off](#turn-the-public-port-off)

## Next

<Columns cols={2}>
  <Card title="Connect ChatGPT" href="/connect/chatgpt">
    a ChatGPT paid plan + a published address · about 10 minutes, once · witnessed end-to-end 2026-08-01
  </Card>

  <Card title="Connect Apple Health" href="/connect/apple-health">
    Health Auto Export on your iPhone · about 10 minutes · push, not pull
  </Card>
</Columns>
