Open two ports to your own devices, and one port to the internet only if claude.ai or ChatGPT needs it.
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.
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.
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 shuts it again whenever you want.
1
Open the Console over SSH, if Postern runs on another computer
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:
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. Press Control-C when you
want to stop. Open the 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.
2
Install Tailscale and turn on two switches
Install Tailscale on the machine Postern runs on, and on every device you want
to reach it from (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.
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:
Then put $TS in front of every tailscale command below. Close that
terminal and $TS is gone. Linux packages put tailscale on your PATH.
Two settings in Tailscale’s admin console decide whether any address below ever
answers. Turn them on in this order.
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.
Open 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. 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.
3
Open two ports to your own devices
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.
--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:
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).The second check needs a connection id, and none exists until you press
Create your ingest endpoint on the
Apple Health card. Then this must print
401 — the address answers, and it wants its secret:
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. 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).
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.
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, never here.
One check before the command: run tailscale serve status and read what this
machine already serves. The line that shows the machine’s name with no port after
it is the 443 slot — the slot the command below takes. Another program may hold
it already. OpenClaw, for example,
can put its own dashboard there, and many people run it on the same machine as
Postern. Because serve and funnel are last-command-wins per port, the command
below takes the slot and the program that held it stops answering — with no
error to that program. Postern cannot take a different slot: a hosted agent
connects out on 443 only. So move the other program to another port first, in
that program’s own settings, and run tailscale serve status again to confirm
the 443 slot is free. The two run side by side on one machine once each holds
its own port.
tailscale funnel --bg --https=443 http://127.0.0.1:8788tailscale 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.
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.
Restart Postern from the folder that holds docker-compose.yml — the postern
folder you made in Install Postern:
cd posterndocker 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.
5
Check it from a device that is not on your tailnet
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:
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.
claude.ai remembers what it found here for about five minutes, failures
included. Change one thing, wait five minutes, then try once.
6
Connect claude.ai or ChatGPT
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.
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.
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.
The next Copy button replaces whatever is on your clipboard — the agent key
too. Store the key first.
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.Revoke a key and every token made from it dies with it.
Postern denies the agent’s next call.
This is not a step. It undoes Open one port to the internet, 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:
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.
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.
Then restart:
cd posterndocker compose restart app
Confirm no funnel remains. The output must contain no AllowFunnel anywhere:
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.
a ChatGPT paid plan + a published address · about 10 minutes, once · witnessed end-to-end 2026-08-01
Connect Apple Health
Health Auto Export on your iPhone · about 10 minutes · push, not pull
{"docsConfig":{"theme":"sequoia","$schema":"https://mintlify.com/docs.json","name":"Postern","colors":{"primary":"#E7A42A","light":"#E7A42A","dark":"#B83E15"},"favicon":"https://mintcdn.com/postern/L5OSSMQX3JjXD-Ob/favicon.svg?fit=max\u0026auto=format\u0026n=L5OSSMQX3JjXD-Ob\u0026q=85\u0026s=acbbe5732970fc6bf76b003f012843f1","appearance":{"default":"light","strict":false},"background":{"color":{"light":"#F5EDDA","dark":"#151F19"}},"navbar":{"links":[{"href":"https://getpostern.com","label":"getpostern.com"}],"primary":{"type":"github","href":"https://github.com/getpostern/postern"}},"footer":{"socials":{"website":"https://getpostern.com","github":"https://github.com/getpostern/postern"}},"fonts":{"heading":{"family":"Cormorant Garamond","source":"fonts/cormorant-garamond-var.woff2","format":"woff2"},"body":{"family":"Source Serif 4","source":"fonts/source-serif-4-var.woff2","format":"woff2"}},"styling":{"codeblocks":"system","latex":false},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"postern","hasAudioNarration":false,"actualSubdomain":"postern","customDomains":["docs.getpostern.com"],"gitSource":{"type":"github","owner":"getpostern","repo":"docs","deployBranch":"main","contentDirectory":"","isPrivate":false,"mountPath":"","label":""},"chroma":{"latestBuiltDeploymentHistoryId":"6a77bc437948e45f24bff423","collectionId":"465e71f9-edfd-4e92-a7b2-36446667a983","liveDeploymentHistoryId":"6a77bc437948e45f24bff423","schemaVersion":"v3"},"entitlements":{"AI_CHAT":{"status":"DISABLED"}},"buildId":"6aea219dcf56d9cceaaf5561:success","clientVersion":"0.0.4076","searchFilterCounts":[{"language":"default","tag":"Reference","count":15},{"language":"default","tag":"Connect a source","count":9},{"language":"default","tag":"Start here","count":6},{"language":"default","tag":"MCP tools","count":5},{"language":"default","tag":"Security","count":4},{"language":"default","tag":"Mail, calendar \u0026 contacts","count":3},{"language":"default","tag":"Agents, keys and grants","count":3},{"language":"default","tag":"The record","count":3},{"language":"default","tag":"Health","count":2},{"language":"default","tag":"Money","count":2}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"rbac-expansion","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-metadata","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-serve","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"live-agents","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"comment-mode","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[{"filePath":"style.css","content":"@font-face{font-family:\"Courier Prime\";src:url(\"/fonts/courier-prime-400.woff2\") format(\"woff2\");font-weight:400;font-style:normal;font-display:swap}@font-face{font-family:\"Courier Prime\";src:url(\"/fonts/courier-prime-700.woff2\") format(\"woff2\");font-weight:700;font-style:normal;font-display:swap}@font-face{font-family:\"Cormorant Garamond\";src:url(\"/fonts/cormorant-garamond-marks.woff2\") format(\"woff2\");font-weight:300 700;font-style:normal;font-display:swap;unicode-range:U+2116, U+2190-2192}@font-face{font-family:\"Cormorant Garamond\";src:url(\"/fonts/cormorant-garamond-italic-marks.woff2\") format(\"woff2\");font-weight:300 700;font-style:italic;font-display:swap;unicode-range:U+2116, U+2190-2192}@font-face{font-family:\"EB Garamond\";src:url(\"/fonts/eb-garamond-marks.woff2\") format(\"woff2\");font-weight:400 800;font-style:normal;font-display:swap;unicode-range:U+2116, U+2190-2192}@font-face{font-family:\"EB Garamond\";src:url(\"/fonts/eb-garamond-italic-marks.woff2\") format(\"woff2\");font-weight:400 800;font-style:italic;font-display:swap;unicode-range:U+2116, U+2190-2192}@font-face{font-family:\"Cormorant Garamond\";src:url(\"/fonts/cormorant-garamond-500-italic.woff2\") format(\"woff2\");font-weight:500;font-style:italic;font-display:swap}@font-face{font-family:\"EB Garamond\";src:url(\"/fonts/eb-garamond-400-italic.woff2\") format(\"woff2\");font-weight:400;font-style:italic;font-display:swap}code,pre,kbd,samp,code span,pre span{font-family:\"Courier Prime\", \"Courier New\", ui-monospace, Menlo, Consolas,\n \"DejaVu Sans Mono\", monospace}@font-face{font-family:\"Source Serif 4\";src:url(\"/fonts/source-serif-4-var.woff2\") format(\"woff2\");font-weight:400 700;font-style:normal;font-display:swap}@font-face{font-family:\"Source Serif 4\";src:url(\"/fonts/source-serif-4-italic-var.woff2\") format(\"woff2\");font-weight:400 700;font-style:italic;font-display:swap}:root{--postern-body: \"Source Serif 4\", \"EB Garamond\", Georgia, serif;--postern-measure: 74ch}#content-area p,#content-area li,#content-area td,#content-area th,#content-area blockquote{font-family:var(--postern-body);font-size:17px;line-height:1.7}#content-area p,#content-area ul,#content-area ol,#content-area blockquote{max-width:var(--postern-measure)}#content-area h2{margin-top:2.75em;margin-bottom:0.6em;letter-spacing:-0.01em}#content-area h3{margin-top:2em;margin-bottom:0.5em}#content-area h2+p,#content-area h3+p{margin-top:0}#content-area table{font-size:15.5px;line-height:1.55}#content-area th{font-size:13px;letter-spacing:0.06em;text-transform:uppercase;opacity:0.72}#content-area td,#content-area th{padding-top:0.6em;padding-bottom:0.6em;vertical-align:baseline}#content-area .prose>table,#content-area table{display:block;overflow-x:auto;max-width:100%}#content-area .callout,#content-area [class*=\"callout\"]{border-width:1px;border-radius:3px}#content-area .callout p,#content-area [class*=\"callout\"] p{font-size:16px;line-height:1.65;max-width:none}#content-area p code,#content-area li code,#content-area td code{font-size:0.94em;padding:0.1em 0.32em;border-radius:2px}#content-area p strong,#content-area li strong,#content-area td strong{font-weight:600;color:var(--postern-strong, inherit)}#content-area h2:has(+p){text-wrap:balance}@media (max-width: 900px){#content-area p,#content-area li,#content-area td,#content-area th,#content-area blockquote{font-size:16px}#content-area h2{margin-top:2.25em}}"}],"page":{"pagePath":"start/remote-access","slug":"/start/remote-access","pageMetadata":{"title":"Set up remote access","description":"Open two ports to your own devices, and one port to the internet only if claude.ai or ChatGPT needs it.","href":"/start/remote-access"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Open two ports to your own devices, and one port to the internet only if claude.ai or ChatGPT needs it.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,li:`li`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,..._provideComponents(),...props.components},{Card,CodeBlock,Columns,Frame,Heading,Info,Note,OptimizedImage,Step,Steps,Table,Warning}=_components;if(!Card)_missingMdxReference(`Card`,true);if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Columns)_missingMdxReference(`Columns`,true);if(!Frame)_missingMdxReference(`Frame`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Info)_missingMdxReference(`Info`,true);if(!Note)_missingMdxReference(`Note`,true);if(!OptimizedImage)_missingMdxReference(`OptimizedImage`,true);if(!Step)_missingMdxReference(`Step`,true);if(!Steps)_missingMdxReference(`Steps`,true);if(!Table)_missingMdxReference(`Table`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsxs(Info,{children:[_jsx(_components.p,{children:_jsx(_components.strong,{children:`Before you start`})}),_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`A Tailscale account, and admin access to Tailscale’s admin console.`}),` The free Personal plan covers a household. Sign up at `,_jsx(_components.a,{href:`https://tailscale.com`,children:`tailscale.com`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Postern installed`}),`, and the Console open in your browser. Step 1 opens it if Postern runs on another computer.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`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.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Your password manager open.`}),` In step 6, Postern shows you an agent key one time only.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`A Mac or a Linux computer.`}),` Postern runs under Docker, so Windows should work. Nobody has run this page there. In PowerShell, `,_jsx(_components.code,{children:`curl`}),` means something else, so type `,_jsx(_components.code,{children:`curl.exe`}),` in the checks below.`]}),`\n`]})]}),`\n`,_jsx(_components.p,{children:`Your tailnet is the private network Tailscale builds between the devices signed\ninto your account. Nothing else can reach it.`}),`\n`,_jsx(_components.p,{children:`Not every step here is for everyone. Read this first, then do the steps that\napply to you, in order.`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Steps`}),_jsx(_components.th,{children:`Who does them`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{\"data-numeric\":`true`,children:_jsx(_components.strong,{children:`1`})}),_jsx(_components.td,{children:`Only you, if Postern runs on a computer you are not sitting at.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`2 and 3`})}),_jsxs(_components.td,{children:[`Anyone who wants their own devices to reach Postern. `,_jsx(_components.strong,{children:`Most people stop here.`})]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`4 to 6`})}),_jsx(_components.td,{children:`Only if claude.ai or ChatGPT must reach Postern. These open one port to the internet.`})]})]})]}),`\n`,_jsxs(_components.p,{children:[`None of this is on until you turn it on. `,_jsx(_components.a,{href:`#turn-the-public-port-off`,children:`Turn the public port\noff`}),` shuts it again whenever you want.`]}),`\n`,_jsxs(Steps,{children:[_jsxs(Step,{title:`Open the Console over SSH, if Postern runs on another computer`,titleSize:`h2`,id:`ssh-tunnel`,children:[_jsx(_components.p,{children:`Skip this if you sit at the machine Postern runs on. The Console only answers on\nthe machine it runs on. Run this on your own computer, not on the machine Postern\nruns on:`}),_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`ssh`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -N`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -L`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` 127.0.0.1:8787:127.0.0.1:8787`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` YOUR_MACHINE`})]}),`\n`]})})}),_jsxs(_components.p,{children:[`Replace `,_jsx(_components.code,{children:`YOUR_MACHINE`}),` with however you reach that computer: a name, a\n`,_jsx(_components.code,{children:`user@host`}),`, or an IP address. The terminal prints nothing and does not return to\na prompt. That is the command at work, not a hang. Leave that window open, then\nopen `,_jsx(_components.a,{href:`http://localhost:8787`,children:`http://localhost:8787`}),`. Press `,_jsx(_components.strong,{children:`Control-C`}),` when you\nwant to stop. `,_jsx(_components.a,{href:`/start/install#open-console`,children:`Open the Console`}),` carries the long\nversion.`]}),_jsxs(_components.p,{children:[`A tunnel does not move the Console off that machine:\n`,_jsx(_components.a,{href:`/reference/ports#the-console-has-no-authentication`,children:`port 8787 has no password, and its address stands in for one`}),`.`]})]}),_jsxs(Step,{title:`Install Tailscale and turn on two switches`,titleSize:`h2`,id:`tailscale`,children:[_jsxs(_components.p,{children:[`Install Tailscale on the machine Postern runs on, and on every device you want\nto reach it from (`,_jsx(_components.a,{href:`https://tailscale.com/download`,children:`tailscale.com/download`}),`).\nSign them all into the same Tailscale account.`]}),_jsxs(_components.p,{children:[`On the machine Postern runs on, run `,_jsx(_components.code,{children:`tailscale up`}),`, then follow what it prints\nto sign that machine in.`]}),_jsxs(Note,{children:[_jsxs(_components.p,{children:[`On a Mac, the App Store and desktop builds leave `,_jsx(_components.code,{children:`tailscale`}),` off your PATH, so\n`,_jsx(_components.code,{children:`which tailscale`}),` returns nothing. It sits inside the app, with a capital T.\nRun this once, in the same terminal:`]}),_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`export`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` TS`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`/`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`Applications`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`/`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`Tailscale`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`app`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`/`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`Contents`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`/`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`MacOS`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`/`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`Tailscale`})]}),`\n`]})})}),_jsxs(_components.p,{children:[`Then put `,_jsx(_components.code,{children:`$TS`}),` in front of every `,_jsx(_components.code,{children:`tailscale`}),` command below. Close that\nterminal and `,_jsx(_components.code,{children:`$TS`}),` is gone. Linux packages put `,_jsx(_components.code,{children:`tailscale`}),` on your PATH.`]})]}),_jsx(_components.p,{children:`Two settings in Tailscale’s admin console decide whether any address below ever\nanswers. Turn them on in this order.`}),_jsx(Warning,{children:_jsx(_components.p,{children:`HTTPS certificates put this machine’s name on the Certificate Transparency\nlog: a public list of every secure-website certificate ever issued. Anyone can\nread it, and entries cannot be removed. Rename the machine to something dull\nfirst, and never treat its name as protection.`})}),_jsxs(_components.p,{children:[`Open `,_jsx(_components.a,{href:`https://login.tailscale.com/admin/dns`,children:`login.tailscale.com/admin/dns`}),`.\nTurn on MagicDNS, which lets your devices reach each other by name. Then turn on\nHTTPS certificates. Tailscale’s own instructions walk both, in that order:\n`,_jsx(_components.a,{href:`https://tailscale.com/kb/1153/enabling-https`,children:`Enabling HTTPS`}),`. Both must be on.\nIf either is off, the failure is silent: the port never answers, and no log\nnames the cause.`]}),_jsxs(_components.p,{children:[`Get this machine’s Tailscale name from `,_jsx(_components.code,{children:`tailscale status`}),` on that machine.\nTailscale’s admin console lists it too, on the page its documentation calls\nMachines. The name looks like `,_jsx(_components.code,{children:`gateway.example-tailnet.ts.net`}),`, and every\ncommand below needs it.`]})]}),_jsxs(Step,{title:`Open two ports to your own devices`,titleSize:`h2`,id:`your-devices`,children:[_jsx(Warning,{children:_jsxs(_components.p,{children:[`Never open port 8787, the Console port, whole to any network. Anything that\nreaches it holds full control of Postern, and no password stands behind it.\nThe first command below opens one address on that port and nothing else.\n`,_jsx(_components.a,{href:`/reference/ports#the-console-has-no-authentication`,children:`Reachability is the authentication`}),`.`]})}),_jsx(_components.p,{children:`Run both on the machine Postern runs on:`}),_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`tailscale`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` serve`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --bg`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --https=8443`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --set-path=/api/ingest`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` http://127.0.0.1:8787/api/ingest`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`tailscale`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` serve`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --bg`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --https=10000`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` http://127.0.0.1:8788`})]}),`\n`]})})}),_jsxs(_components.p,{children:[_jsx(_components.code,{children:`--bg`}),` keeps Tailscale in the background and gives your prompt back. Without it\nthe terminal is stuck. Both commands work on Tailscale 1.98.9, and `,_jsx(_components.code,{children:`--set-path`}),`\nis current syntax there.`]}),_jsx(_components.p,{children:`Your own devices now reach two addresses, and the internet reaches neither:`}),_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`https://gateway.example-tailnet.ts.net:8443/api/ingest/\u003cconnection-id>`}),` — your iPhone pushes Apple Health data here.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`https://gateway.example-tailnet.ts.net:10000/mcp`}),` — an agent on your own devices connects here, with an agent key.`]}),`\n`]}),_jsx(_components.p,{children:`Check it from a second computer on your tailnet. It needs Tailscale, the same\naccount, and a terminal, so a phone will not do. Substitute your own machine\nname. This prints only the three-digit answer code:`}),_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`curl`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -so`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` /dev/null`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -w`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` '%{http_code}'`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` https://gateway.example-tailnet.ts.net:8443/api/agents`})]}),`\n`]})})}),_jsxs(_components.p,{children:[`It must print `,_jsx(_components.strong,{children:`404`}),` — nothing is there, because the first command opened\n`,_jsx(_components.code,{children:`/api/ingest`}),` alone. `,_jsx(_components.strong,{children:`200`}),` means the whole port is open: stop, and run both\ncommands again exactly. Nothing at all means MagicDNS or HTTPS certificates is\noff (`,_jsx(_components.a,{href:`#tailscale`,children:`step 2`}),`).`]}),_jsxs(_components.p,{children:[`The second check needs a connection id, and none exists until you press\n`,_jsx(_components.strong,{children:`Create your ingest endpoint`}),` on the\n`,_jsx(_components.a,{href:`/connect/apple-health#create-endpoint`,children:`Apple Health`}),` card. Then this must print\n`,_jsx(_components.strong,{children:`401`}),` — the address answers, and it wants its secret:`]}),_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`curl`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -so`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` /dev/null`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -w`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` '%{http_code}'`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` https://gateway.example-tailnet.ts.net:8443/api/ingest/`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`\u003c`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`connection-i`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`d`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`>`})]}),`\n`]})})})]}),_jsxs(Step,{title:`Open one port to the internet for a hosted agent`,titleSize:`h2`,id:`publish`,children:[_jsx(_components.p,{children:`Do this only for a hosted agent: an AI app that runs on a company’s servers, like\nclaude.ai or ChatGPT. It can never reach your home network on its own, and it\nconnects out on port 443 only. Open 443, pointed at port 8788, the agent port.\nNever point it at 8787.`}),_jsxs(_components.p,{children:[`Your tailnet policy must let this machine run a funnel. Tailscale keeps that\npolicy in its admin console, on the page its documentation calls Access\ncontrols. Tailscale’s own instructions cover the funnel permission:\n`,_jsx(_components.a,{href:`https://tailscale.com/kb/1223/funnel`,children:`Tailscale Funnel`}),`. Scope the permission to\nthis machine’s own tag, never to the whole tailnet. `,_jsx(_components.code,{children:`serve`}),` and `,_jsx(_components.code,{children:`funnel`}),` are\nlast-command-wins per port. A mistyped command on any machine that holds the\npermission can open something to the internet\n(`,_jsx(_components.a,{href:`/reference/ports#reaching-postern-from-another-network`,children:`the whole argument`}),`).\nThis page does not walk Tailscale’s policy file. If the permission is not there,\nthe command below refuses out loud and prints a link that turns it on. Open that\nlink, then run the command again.`]}),_jsx(Warning,{children:_jsxs(_components.p,{children:[`Run the command below once, and get it right first time. Repeated `,_jsx(_components.code,{children:`serve`}),` and\n`,_jsx(_components.code,{children:`funnel`}),` commands re-issue certificates, and the free certificate service\nlimits how often it will issue one for a name. Past that limit you lose your\nown hostname for hours. Retry in `,_jsx(_components.a,{href:`#probe-outside`,children:`step 5`}),`, never here.`]})}),_jsxs(_components.p,{children:[`One check before the command: run `,_jsx(_components.code,{children:`tailscale serve status`}),` and read what this\nmachine already serves. The line that shows the machine’s name with no port after\nit is the 443 slot — the slot the command below takes. Another program may hold\nit already. `,_jsx(_components.a,{href:`https://docs.openclaw.ai/gateway/tailscale`,children:`OpenClaw`}),`, for example,\ncan put its own dashboard there, and many people run it on the same machine as\nPostern. Because `,_jsx(_components.code,{children:`serve`}),` and `,_jsx(_components.code,{children:`funnel`}),` are last-command-wins per port, the command\nbelow takes the slot and the program that held it stops answering — with no\nerror to that program. Postern cannot take a different slot: a hosted agent\nconnects out on 443 only. So move the other program to another port first, in\nthat program’s own settings, and run `,_jsx(_components.code,{children:`tailscale serve status`}),` again to confirm\nthe 443 slot is free. The two run side by side on one machine once each holds\nits own port.`]}),_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`tailscale`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` funnel`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --bg`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --https=443`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` http://127.0.0.1:8788`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`tailscale`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` funnel`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` status`})]}),`\n`]})})}),_jsxs(_components.p,{children:[`In `,_jsx(_components.code,{children:`tailscale funnel status`}),`, the 443 line must read `,_jsx(_components.code,{children:`(Funnel on)`}),`, and every\nother line `,_jsx(_components.code,{children:`(tailnet only)`}),`.`]}),_jsxs(_components.p,{children:[`Now tell Postern its address. In the Console, click `,_jsx(_components.strong,{children:`Settings`}),` in the left\nsidebar, then find `,_jsx(_components.strong,{children:`Remote access`}),`. In the row headed `,_jsx(_components.strong,{children:`Public · the address\nthis gate answers on`}),`, press `,_jsx(_components.strong,{children:`Set the address…`}),`.`]}),_jsxs(_components.p,{children:[`A field labelled `,_jsx(_components.strong,{children:`Your gate’s public address`}),` appears, with the placeholder\n`,_jsx(_components.code,{children:`https://your-mac.tailnet.ts.net`}),`. Type `,_jsx(_components.code,{children:`https://`}),` and this machine’s Tailscale\nname, with nothing after it — not the `,_jsx(_components.code,{children:`/mcp`}),` address you hand an agent. Postern\nbuilds that one itself, and it refuses an address that carries a path.`]}),_jsxs(_components.p,{children:[`Postern does not save the address until you confirm it. Press `,_jsx(_components.strong,{children:`Publish this\ngate`}),` on the bar headed `,_jsx(_components.strong,{children:`Publish this gate to the public internet?`}),`. That bar\nlists what it costs you. The row then reads `,_jsx(_components.strong,{children:`restart to apply`}),`, and a message\nreads `,_jsx(_components.code,{children:`Saved — restart the gateway to start publishing.`})]}),_jsx(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.`,children:_jsx(OptimizedImage,{src:`https://mintcdn.com/postern/E8R3wfogdGSukifu/images/remote-console-card.png?fit=max\u0026auto=format\u0026n=E8R3wfogdGSukifu\u0026q=85\u0026s=7a39a80ce863d524cd6f2f5076115a53`,alt:`Settings → Remote access: the Public row reads off, with a Set the address button.`,\"data-og-width\":`1440`,width:`1440`,\"data-og-height\":`1355`,height:`1355`,\"data-path\":`images/remote-console-card.png`,\"data-optimize\":`true`,\"data-opv\":`3`,srcset:`https://mintcdn.com/postern/E8R3wfogdGSukifu/images/remote-console-card.png?w=280\u0026fit=max\u0026auto=format\u0026n=E8R3wfogdGSukifu\u0026q=85\u0026s=f46ea4a8993512e597dae0a9f7c02b65 280w, https://mintcdn.com/postern/E8R3wfogdGSukifu/images/remote-console-card.png?w=560\u0026fit=max\u0026auto=format\u0026n=E8R3wfogdGSukifu\u0026q=85\u0026s=94515b85d65cf2870d38dcc87fea5c85 560w, https://mintcdn.com/postern/E8R3wfogdGSukifu/images/remote-console-card.png?w=840\u0026fit=max\u0026auto=format\u0026n=E8R3wfogdGSukifu\u0026q=85\u0026s=cf6406e6d2315e0fc85adbccbb1ec9d0 840w, https://mintcdn.com/postern/E8R3wfogdGSukifu/images/remote-console-card.png?w=1100\u0026fit=max\u0026auto=format\u0026n=E8R3wfogdGSukifu\u0026q=85\u0026s=6839f0a236a791e71fe4f6b4e3317e02 1100w, https://mintcdn.com/postern/E8R3wfogdGSukifu/images/remote-console-card.png?w=1650\u0026fit=max\u0026auto=format\u0026n=E8R3wfogdGSukifu\u0026q=85\u0026s=b3b75f313ed82a800f197721e2b9a558 1650w, https://mintcdn.com/postern/E8R3wfogdGSukifu/images/remote-console-card.png?w=2500\u0026fit=max\u0026auto=format\u0026n=E8R3wfogdGSukifu\u0026q=85\u0026s=41dbe40e80cda3639d7071dd495d78f2 2500w`})}),_jsxs(_components.p,{children:[`Restart Postern from the folder that holds `,_jsx(_components.code,{children:`docker-compose.yml`}),` — the `,_jsx(_components.code,{children:`postern`}),`\nfolder you made in `,_jsx(_components.a,{href:`/start/install`,children:`Install Postern`}),`:`]}),_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#DCDCAA`},children:`cd`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` postern`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`docker`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` compose`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` restart`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` app`})]}),`\n`]})})}),_jsxs(_components.p,{children:[`The restart is not optional: Postern reads the address once, at start-up. The\n`,_jsx(_components.strong,{children:`Public`}),` row then reads `,_jsx(_components.strong,{children:`published`}),`, and the `,_jsx(_components.strong,{children:`OAuth bridge`}),` row reads\n`,_jsx(_components.strong,{children:`On — published at`}),` your address. That bridge is on exactly when Postern has\nan address, and off otherwise. There is no separate switch.`]})]}),_jsxs(Step,{title:`Check it from a device that is not on your tailnet`,titleSize:`h2`,id:`probe-outside`,children:[_jsxs(_components.p,{children:[`A device on your tailnet cannot prove this: from inside, a funnel and a private\nserve look identical. Use a device off your tailnet that can run these commands\n— a laptop on a phone’s cellular hotspot does it. Substitute your own machine\nname. All three must print `,_jsx(_components.strong,{children:`200`}),`, `,_jsx(_components.strong,{children:`401`}),` and `,_jsx(_components.strong,{children:`404`}),`, in that order:`]}),_jsx(CodeBlock,{filename:``,numberOfLines:`3`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`3`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`curl`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -so`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` /dev/null`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -w`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` '%{http_code}'`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` https://gateway.example-tailnet.ts.net/.well-known/oauth-protected-resource/mcp`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`curl`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -so`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` /dev/null`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -w`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` '%{http_code}'`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` https://gateway.example-tailnet.ts.net/mcp`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`curl`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -so`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` /dev/null`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -w`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` '%{http_code}'`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` https://gateway.example-tailnet.ts.net/api/agents`})]}),`\n`]})})}),_jsx(_components.p,{children:`200 means it answered. 401 means it answered and wants a key. 404 means nothing\nis there. On the third line that is the answer you want: the Console port did\nnot come with it. Touch no agent app until all three read correctly.`}),_jsx(Note,{children:_jsx(_components.p,{children:`claude.ai remembers what it found here for about five minutes, failures\nincluded. Change one thing, wait five minutes, then try once.`})})]}),_jsxs(Step,{title:`Connect claude.ai or ChatGPT`,titleSize:`h2`,id:`connect-agent`,children:[_jsxs(_components.p,{children:[`First create the key. In the Console, click `,_jsx(_components.strong,{children:`Agents \u0026 keys`}),` in the left\nsidebar, then click `,_jsx(_components.strong,{children:`Mint a key`}),`. Three fields:`]}),_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Name`}),` — what you will call this agent.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Grant which sectors`}),` — six areas of your life: finance, mail, calendar, contacts, health, home. The form says `,_jsx(_components.code,{children:`Nothing is granted until you pick it.`})]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Expires`}),` — four chips: `,_jsx(_components.strong,{children:`Never`}),`, `,_jsx(_components.strong,{children:`30 days`}),`, `,_jsx(_components.strong,{children:`60 days`}),` and `,_jsx(_components.strong,{children:`90 days`}),`. Once you have set a public address, the form starts on `,_jsx(_components.strong,{children:`90 days`}),`.`]}),`\n`]}),_jsxs(_components.p,{children:[_jsx(_components.a,{href:`/start/first-key`,children:`Mint your first key`}),` walks the whole form.`]}),_jsx(Warning,{children:_jsxs(_components.p,{children:[`Pick anything but `,_jsx(_components.strong,{children:`Never`}),`. Postern refuses a key with no expiry at the hosted\nsign-in page: `,_jsx(_components.code,{children:`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 `,_jsx(_components.strong,{children:`90 days`}),` is\nits longest. `,_jsx(_components.strong,{children:`Never`}),` dead-ends you at the last step of this page.`]})}),_jsxs(_components.p,{children:[`Press `,_jsx(_components.strong,{children:`Mint the key`}),`. A screen headed `,_jsx(_components.strong,{children:`Agent key — shown once`}),` appears, with\na `,_jsx(_components.strong,{children:`Copy`}),` button. Copy the key into your password manager now. Postern cannot\nshow you this key again. It keeps a scrambled copy it can check against, never\nthe key itself. Lose it and you create another.`]}),_jsx(Warning,{children:_jsxs(_components.p,{children:[`The next `,_jsx(_components.strong,{children:`Copy`}),` button replaces whatever is on your clipboard — the agent key\ntoo. Store the key first.`]})}),_jsxs(_components.p,{children:[`The address you hand the agent is your published address plus `,_jsx(_components.code,{children:`/mcp`}),`. The\nConsole prints the exact value under `,_jsx(_components.strong,{children:`Agents \u0026 keys`}),` → `,_jsx(_components.strong,{children:`Set up the bridge →`}),`,\nwith its own `,_jsx(_components.strong,{children:`Copy`}),` button.`]}),_jsx(_components.p,{children:`In claude.ai, open its settings and find its connectors. Add a connector of your\nown. Paste the address into it. If it asks for a client id or a client secret,\nleave both empty. Postern issues neither.`}),_jsxs(_components.p,{children:[`claude.ai then sends you to a page your own Postern serves, headed `,_jsx(_components.strong,{children:`Authorize\nagent access`}),`. Paste the agent key under `,_jsx(_components.strong,{children:`Paste an agent key from the Console`}),`.\nPress `,_jsx(_components.strong,{children:`Approve`}),`. claude.ai holds a token that rotates from then on. ChatGPT\ntakes a longer path: `,_jsx(_components.a,{href:`/connect/chatgpt`,children:`Connect ChatGPT`}),`.`]}),_jsxs(_components.p,{children:[_jsx(_components.a,{href:`/reference/agent-keys#revoking-rotating-and-removing`,children:`Revoke a key and every token made from it dies with it`}),`.\nPostern denies the agent’s next call.`]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`turn-the-public-port-off`,children:`Turn the public port off`}),`\n`,_jsxs(_components.p,{children:[`This is not a step. It undoes `,_jsx(_components.a,{href:`#publish`,children:`Open one port to the internet`}),`, whenever\nyou want that port shut again. Nothing here touches your own devices.`]}),`\n`,_jsx(_components.p,{children:`Three acts. Do all three. Any two leave you half-off — a funnel with no address,\nan address with no funnel, or Postern still on the old one.`}),`\n`,_jsx(_components.p,{children:`On the machine Postern runs on:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`tailscale`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` funnel`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --https=443`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` off`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`In the Console, click `,_jsx(_components.strong,{children:`Settings`}),`, then find `,_jsx(_components.strong,{children:`Remote access`}),`. In the\n`,_jsx(_components.strong,{children:`Public`}),` row, press `,_jsx(_components.strong,{children:`Stop publishing…`}),`, then press `,_jsx(_components.strong,{children:`Stop publishing`}),` on\nthe bar headed `,_jsx(_components.strong,{children:`Stop publishing this gate?`}),`. A message reads\n`,_jsx(_components.code,{children:`Stopped — it stays reachable until the gateway restarts.`})]}),`\n`,_jsx(Note,{children:_jsxs(_components.p,{children:[`If `,_jsx(_components.code,{children:`.env`}),` on the machine Postern runs on holds an `,_jsx(_components.code,{children:`MCP_PUBLIC_URL`}),` line, the\nConsole refuses, and says so: `,_jsx(_components.code,{children:`The box publishes this gateway: MCP_PUBLIC_URL is set in its environment, and the box wins over anything saved here.`}),` Delete\nthat line from `,_jsx(_components.code,{children:`.env`}),` first. `,_jsx(_components.code,{children:`.env`}),` is the settings file beside\n`,_jsx(_components.code,{children:`docker-compose.yml`}),`.`]})}),`\n`,_jsx(_components.p,{children:`Then restart:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#DCDCAA`},children:`cd`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` postern`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`docker`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` compose`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` restart`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` app`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Confirm no funnel remains. The output must contain no `,_jsx(_components.code,{children:`AllowFunnel`}),` anywhere:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`tailscale`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` serve`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` status`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --json`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Nothing about `,_jsx(_components.a,{href:`#your-devices`,children:`Open two ports to your own devices`}),` changes. Your own\ndevices still reach Postern.`]}),`\n`,_jsx(Heading,{level:`2`,id:`confirm-it-works`,children:`Confirm it works`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Tailscale’s admin console → DNS: MagicDNS on, HTTPS certificates on.`}),`\n`,_jsxs(_components.li,{children:[`From a second computer on your tailnet, `,_jsx(_components.code,{children:`/api/agents`}),` on port 8443 prints `,_jsx(_components.strong,{children:`404`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`tailscale funnel status`}),`: the 443 line reads `,_jsx(_components.code,{children:`(Funnel on)`}),`, every other line `,_jsx(_components.code,{children:`(tailnet only)`}),`.`]}),`\n`,_jsxs(_components.li,{children:[`Console → Settings → Remote access: the `,_jsx(_components.strong,{children:`Public`}),` row reads `,_jsx(_components.strong,{children:`published`}),`, not `,_jsx(_components.strong,{children:`restart to apply`}),`.`]}),`\n`,_jsxs(_components.li,{children:[`Console → Settings → Remote access: the `,_jsx(_components.strong,{children:`OAuth bridge`}),` row reads `,_jsx(_components.strong,{children:`On — published at`}),` your address.`]}),`\n`,_jsxs(_components.li,{children:[`From a device off your tailnet, the three checks in `,_jsx(_components.a,{href:`#probe-outside`,children:`step 5`}),` print `,_jsx(_components.strong,{children:`200`}),`, `,_jsx(_components.strong,{children:`401`}),` and `,_jsx(_components.strong,{children:`404`}),`.`]}),`\n`,_jsxs(_components.li,{children:[`Console → `,_jsx(_components.strong,{children:`Agents \u0026 keys`}),` lists your key, and its sub-line reads `,_jsx(_components.code,{children:`expires in 30d`}),`, `,_jsx(_components.code,{children:`expires in 60d`}),` or `,_jsx(_components.code,{children:`expires in 90d`}),` — never `,_jsx(_components.code,{children:`never expires`}),`.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`if-something-went-wrong`,children:`If something went wrong`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`What you see`}),_jsx(_components.th,{children:`What to do`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`The port never answers, and nothing in any log says why`}),_jsxs(_components.td,{children:[`MagicDNS or HTTPS certificates is off. Turn both on at `,_jsx(_components.a,{href:`https://login.tailscale.com/admin/dns`,children:`login.tailscale.com/admin/dns`}),`.`]})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[_jsx(_components.code,{children:`which tailscale`}),` returns nothing, on a Mac`]}),_jsxs(_components.td,{children:[`Run `,_jsx(_components.code,{children:`export TS=/Applications/Tailscale.app/Contents/MacOS/Tailscale`}),`, then put `,_jsx(_components.code,{children:`$TS`}),` in front of every `,_jsx(_components.code,{children:`tailscale`}),` command.`]})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[_jsx(_components.code,{children:`curl`}),` prints a web page instead of three digits, on Windows`]}),_jsxs(_components.td,{children:[`In PowerShell, `,_jsx(_components.code,{children:`curl`}),` means something else. Type `,_jsx(_components.code,{children:`curl.exe`}),` instead.`]})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[_jsx(_components.code,{children:`/api/agents`}),` answers 200 from your tailnet`]}),_jsxs(_components.td,{children:[`Port 8787 is open whole. Run both commands in `,_jsx(_components.a,{href:`#your-devices`,children:`step 3`}),` again, exactly as written.`]})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[`The `,_jsx(_components.strong,{children:`Public`}),` row still reads `,_jsx(_components.strong,{children:`off`}),` after you typed the address`]}),_jsxs(_components.td,{children:[`Postern saves the address only when you press `,_jsx(_components.strong,{children:`Publish this gate`}),` on the confirmation bar (`,_jsx(_components.a,{href:`#publish`,children:`step 4`}),`).`]})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[`The `,_jsx(_components.strong,{children:`Public`}),` row reads `,_jsx(_components.strong,{children:`restart to apply`})]}),_jsxs(_components.td,{children:[`Postern reads the address only at start-up. Run `,_jsx(_components.code,{children:`docker compose restart app`}),` from the folder that holds `,_jsx(_components.code,{children:`docker-compose.yml`}),`.`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`A hosted agent says it could not reach you, and your logs show nothing`}),_jsxs(_components.td,{children:[`The funnel is on a port other than 443, so the request never reaches you. Run `,_jsx(_components.code,{children:`tailscale funnel status`}),`; the 443 line must read `,_jsx(_components.code,{children:`(Funnel on)`}),`.`]})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`what-you-have-now`,children:`What you have now`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`If you stopped after`}),_jsx(_components.th,{children:`What can reach Postern`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.a,{href:`#ssh-tunnel`,children:`step 1`})}),_jsx(_components.td,{children:`Nothing is open. The Console reaches your computer through the SSH tunnel, and nowhere else.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.a,{href:`#your-devices`,children:`step 3`})}),_jsx(_components.td,{children:`Your own devices reach two addresses on your tailnet. The internet reaches nothing.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.a,{href:`#connect-agent`,children:`step 6`})}),_jsxs(_components.td,{children:[`The internet reaches eight addresses on one port. Seven are the sign-in machinery and hold no data of yours; the eighth is `,_jsx(_components.code,{children:`/mcp`}),`, which needs a key. The Console is not among them. `,_jsx(_components.a,{href:`/reference/oauth-bridge#what-the-internet-reaches`,children:`The full list`}),`.`]})]})]})]}),`\n`,_jsx(_components.p,{children:`Three costs stay with you:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`a public hostname in a permanent certificate log`}),`\n`,_jsx(_components.li,{children:`a Tailscale account to keep`}),`\n`,_jsxs(_components.li,{children:[`an address that stays live until you `,_jsx(_components.a,{href:`#turn-the-public-port-off`,children:`turn it off`})]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`next`,children:`Next`}),`\n`,_jsxs(Columns,{cols:2,children:[_jsx(Card,{title:`Connect ChatGPT`,href:`/connect/chatgpt`,children:_jsx(_components.p,{children:`a ChatGPT paid plan + a published address · about 10 minutes, once · witnessed end-to-end 2026-08-01`})}),_jsx(Card,{title:`Connect Apple Health`,href:`/connect/apple-health`,children:_jsx(_components.p,{children:`Health Auto Export on your iPhone · about 10 minutes · push, not pull`})})]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"codeStyling":"system","config":{},"pageMetadata":{"title":"Set up remote access","description":"Open two ports to your own devices, and one port to the internet only if claude.ai or ChatGPT needs it.","href":"/start/remote-access"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Open the Console over SSH, if Postern runs on another computer","slug":"ssh-tunnel","depth":2,"children":[]},{"title":"Install Tailscale and turn on two switches","slug":"tailscale","depth":2,"children":[]},{"title":"Open two ports to your own devices","slug":"your-devices","depth":2,"children":[]},{"title":"Open one port to the internet for a hosted agent","slug":"publish","depth":2,"children":[]},{"title":"Check it from a device that is not on your tailnet","slug":"probe-outside","depth":2,"children":[]},{"title":"Connect claude.ai or ChatGPT","slug":"connect-agent","depth":2,"children":[]},{"title":"Turn the public port off","slug":"turn-the-public-port-off","depth":2,"children":[]},{"title":"Confirm it works","slug":"confirm-it-works","depth":2,"children":[]},{"title":"If something went wrong","slug":"if-something-went-wrong","depth":2,"children":[]},{"title":"What you have now","slug":"what-you-have-now","depth":2,"children":[]},{"title":"Next","slug":"next","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"postern","siteName":"Postern","title":"Set up remote access - Postern","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-08-08T18:21:30.371Z","originalFileLocation":"start/remote-access.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"light\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Open two ports to your own devices, and one port to the internet only if claude.ai or ChatGPT needs it.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Postern\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Set up remote access - Postern\">\u003cmeta data-mint-head property=\"og:description\" content=\"Open two ports to your own devices, and one port to the internet only if claude.ai or ChatGPT needs it.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Postern\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.getpostern.com/start/remote-access\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://postern.mintlify.site/_mintlify/api/og/v2?division=Start+here\u0026amp;title=Set+up+remote+access\u0026amp;description=Open+two+ports+to+your+own+devices%2C+and+one+port+to+the+internet+only+if+claude.ai+or+ChatGPT+needs+it.\u0026amp;theme=4d380beff7db0b8a6a5f24a8\u0026amp;signature=18c982c09d58ced386bafe9ff6c552a37127580e2403fad0c8549a962433f829\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Set up remote access - Postern\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Open two ports to your own devices, and one port to the internet only if claude.ai or ChatGPT needs it.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://postern.mintlify.site/_mintlify/api/og/v2?division=Start+here\u0026amp;title=Set+up+remote+access\u0026amp;description=Open+two+ports+to+your+own+devices%2C+and+one+port+to+the+internet+only+if+claude.ai+or+ChatGPT+needs+it.\u0026amp;theme=4d380beff7db0b8a6a5f24a8\u0026amp;signature=18c982c09d58ced386bafe9ff6c552a37127580e2403fad0c8549a962433f829\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Postern\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#E7A42A\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.getpostern.com/start/remote-access\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/start/remote-access.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.getpostern.com/#organization\",\"name\":\"Postern\",\"url\":\"https://docs.getpostern.com\"},{\"@type\":\"WebSite\",\"@id\":\"https://docs.getpostern.com#website\",\"name\":\"Postern\",\"url\":\"https://docs.getpostern.com\",\"publisher\":{\"@id\":\"https://docs.getpostern.com/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.getpostern.com/start/remote-access#webpage\",\"url\":\"https://docs.getpostern.com/start/remote-access\",\"name\":\"Set up remote access\",\"description\":\"Open two ports to your own devices, and one port to the internet only if claude.ai or ChatGPT needs it.\",\"dateModified\":\"2026-08-08T18:21:30.371Z\",\"isPartOf\":{\"@id\":\"https://docs.getpostern.com#website\"},\"breadcrumb\":{\"@id\":\"https://docs.getpostern.com/start/remote-access#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.getpostern.com/start/remote-access#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Start here\",\"item\":\"https://docs.getpostern.com/index\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Set up remote access\",\"item\":\"https://docs.getpostern.com/start/remote-access\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.getpostern.com/start/remote-access#article\",\"headline\":\"Set up remote access\",\"name\":\"Set up remote access\",\"description\":\"Open two ports to your own devices, and one port to the internet only if claude.ai or ChatGPT needs it.\",\"url\":\"https://docs.getpostern.com/start/remote-access\",\"mainEntityOfPage\":{\"@id\":\"https://docs.getpostern.com/start/remote-access#webpage\"},\"dateModified\":\"2026-08-08T18:21:30.371Z\",\"publisher\":{\"@id\":\"https://docs.getpostern.com/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.getpostern.com#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Postern\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Postern","nav":{"global":null,"groups":[{"group":"Start here","pages":[{"title":"Postern","href":"/index"},{"title":"Install Postern","description":"Run Postern under Docker on a machine you own, then open its Console.","href":"/start/install"},{"title":"Set up remote access","href":"/start/remote-access"},{"title":"Mint your first key","description":"Create one agent key in the Console: name it, tick the sectors it may reach, set when it expires, and store it before you leave the screen.","href":"/start/first-key"},{"title":"Connect an agent","href":"/start/connect-an-agent"},{"title":"Set up with an agent","href":"/start/set-up-with-an-agent"}]},{"group":"Connect a source","pages":[{"group":"Money","pages":[{"title":"Connect SimpleFIN","href":"/connect/simplefin"},{"title":"Connect Plaid","href":"/connect/plaid"}]},{"group":"Health","pages":[{"title":"Connect Apple Health","href":"/connect/apple-health"},{"title":"Connect WHOOP","href":"/connect/whoop"}]},{"group":"Mail, calendar \u0026 contacts","pages":[{"title":"Connect Google and Gmail","href":"/connect/google"},{"title":"Connect Microsoft","href":"/connect/microsoft"},{"title":"Connect iCloud","href":"/connect/icloud"}]},{"group":"Home","pages":[{"title":"Connect Home Assistant","href":"/connect/home-assistant"}]},{"group":"Agents","pages":[{"title":"Connect ChatGPT","href":"/connect/chatgpt"}]}]},{"group":"Operate","pages":[{"title":"Back up, export and erase","href":"/operate/backup-and-export"}]},{"group":"Reference","pages":[{"group":"Security","pages":[{"title":"Security model","href":"/reference/security-model"},{"title":"Which computers can reach Postern","href":"/reference/ports"},{"title":"The vault and the master key","href":"/reference/vault"},{"title":"Provider sign-ins","href":"/reference/provider-sign-ins"}]},{"group":"Agents, keys and grants","pages":[{"title":"Grants and sectors","href":"/reference/grants-and-sectors"},{"title":"Agent keys: create, expire, revoke, rotate","href":"/reference/agent-keys"},{"title":"Why an agent was refused","href":"/reference/refusals"}]},{"group":"MCP tools","pages":[{"title":"MCP tools","href":"/reference/mcp-primitives"},{"title":"Read data: get_schema, query, get_record","href":"/reference/mcp-reading"},{"title":"Freshness and live reads: describe_context, fetch_live","href":"/reference/mcp-freshness"},{"title":"Act on a source: list_actions, invoke_action","href":"/reference/mcp-actions"},{"title":"The OAuth bridge, for hosted agents","href":"/reference/oauth-bridge"}]},{"group":"The record","pages":[{"title":"The audit log","href":"/reference/audit-log"},{"title":"Read the record","href":"/reference/read-the-record"},{"title":"What the record cannot promise","href":"/reference/record-limits"}]}]},{"group":"About","pages":[{"title":"Frequently asked questions","href":"/about/faq"}]}]},"basePath":"","title":"Set up remote access - Postern","buildId":"dpl_8LJbrAiZvr1K98WQNZXCyY5AvT34","deploymentId":"dpl_8LJbrAiZvr1K98WQNZXCyY5AvT34","dynamics":["07c022d89b","a74be98ba7"],"chunks":["Frame-NdetNziF.js","DynamicMarkdown-CD8713Yy.js","zoom-image-lightbox-DMoL6lHz.js","lightbox-handle-r17bVUz3.js"]}