[{"data":1,"prerenderedAt":619},["ShallowReactive",2],{"content-query-VZKoBDr2y0":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"layout":10,"crumb":11,"term":11,"summary":12,"group":13,"order":14,"updated":15,"cta":16,"related":24,"faq":42,"body":55,"_type":612,"_id":613,"_source":614,"_file":615,"_stem":616,"_extension":617,"sitemap":618},"/glossary/tunnel","glossary",false,"","What is a tunnel (localhost tunnel)? How it works","A localhost tunnel gives a server on your machine or private network a public URL by relaying traffic over an outbound connection. How tunnels work, when to use one, and common pitfalls.","reference","Tunnel","A public URL for a local server, relayed over an outbound connection.","Infrastructure",32,"2026-10-28",{"eyebrow":17,"title":18,"text":19,"to":20,"label":21,"secondaryTo":22,"secondaryLabel":23},"Webhook Relay tunnels","A stable public HTTPS URL for localhost","Run relay connect --name my-app http://localhost:3000 and your local server gets a public HTTPS address that stays the same as long as you reuse the name. HTTP and TCP tunnels, multiple regions, no open ports.","/tunnels/","See tunnels","/webhook-gateway/","Webhook forwarding",[25,28,30,33,36,39],{"title":26,"to":27},"Reverse proxy","/glossary/reverse-proxy/",{"title":23,"to":29},"/glossary/webhook-forwarding/",{"title":31,"to":32},"localhost:3000","/localhost/3000/",{"title":34,"to":35},"127.0.0.1","/localhost/127-0-0-1/",{"title":37,"to":38},"ERR_CERT_AUTHORITY_INVALID","/http-errors/err-cert-authority-invalid/",{"title":40,"to":41},"ngrok alternative","/blog/ngrok-alternative/",[43,46,49,52],{"q":44,"a":45},"What is a localhost tunnel?","A localhost tunnel gives a server running on your machine or private network a public URL. An agent on your machine opens an outbound connection to a tunnel service, and the service relays requests from the public URL back over that connection to your local port, so no firewall port or public IP is needed.",{"q":47,"a":48},"Are tunnels safe?","A tunnel makes your local server reachable by anyone who knows the URL, so treat it like a public deployment. Don't expose admin panels or databases without authentication, prefer HTTPS, close the tunnel when you're done, and be aware that some tunnel URLs are guessable or listed in logs.",{"q":50,"a":51},"What is the difference between a tunnel and a VPN?","A VPN connects a device into a private network so it can reach many internal hosts. A localhost tunnel goes the other way and exposes one specific local service to the public internet through a hostname. VPNs are for private access; tunnels are for public access to one port.",{"q":53,"a":54},"Should I use a tunnel or webhook forwarding for webhooks?","Either works, but webhook forwarding is usually better for webhooks. A tunnel exposes the whole port and drops requests when your machine is offline. Webhook forwarding only relays webhook requests, can store and retry them while you are offline, and can deliver one webhook to several destinations.",{"type":56,"children":57,"toc":605},"root",[58,72,127,134,189,201,209,215,286,292,304,419,440,446,579,599],{"type":59,"tag":60,"props":61,"children":62},"element","p",{},[63,70],{"type":59,"tag":64,"props":65,"children":66},"strong",{},[67],{"type":68,"value":69},"text","A localhost tunnel gives a server running on your machine or a private network a public URL.",{"type":68,"value":71}," An agent on your side opens an outbound connection to a tunnel service, and the service relays any request sent to the public hostname back down that connection to your local port. Your server becomes reachable from the internet without a public IP, port forwarding or firewall changes.",{"type":59,"tag":73,"props":74,"children":78},"pre",{"code":75,"language":76,"meta":7,"className":77,"style":7},"relay connect --name my-app http://localhost:3000\n# prints a public HTTPS URL that forwards to http://localhost:3000\n","bash","language-bash shiki shiki-themes github-dark",[79],{"type":59,"tag":80,"props":81,"children":82},"code",{"__ignoreMap":7},[83,117],{"type":59,"tag":84,"props":85,"children":88},"span",{"class":86,"line":87},"line",1,[89,95,101,107,112],{"type":59,"tag":84,"props":90,"children":92},{"style":91},"--shiki-default:#B392F0",[93],{"type":68,"value":94},"relay",{"type":59,"tag":84,"props":96,"children":98},{"style":97},"--shiki-default:#9ECBFF",[99],{"type":68,"value":100}," connect",{"type":59,"tag":84,"props":102,"children":104},{"style":103},"--shiki-default:#79B8FF",[105],{"type":68,"value":106}," --name",{"type":59,"tag":84,"props":108,"children":109},{"style":97},[110],{"type":68,"value":111}," my-app",{"type":59,"tag":84,"props":113,"children":114},{"style":97},[115],{"type":68,"value":116}," http://localhost:3000\n",{"type":59,"tag":84,"props":118,"children":120},{"class":86,"line":119},2,[121],{"type":59,"tag":84,"props":122,"children":124},{"style":123},"--shiki-default:#6A737D",[125],{"type":68,"value":126},"# prints a public HTTPS URL that forwards to http://localhost:3000\n",{"type":59,"tag":128,"props":129,"children":131},"h2",{"id":130},"how-a-tunnel-works",[132],{"type":68,"value":133},"How a tunnel works",{"type":59,"tag":135,"props":136,"children":137},"ol",{},[138,155,167,172,177],{"type":59,"tag":139,"props":140,"children":141},"li",{},[142,144,153],{"type":68,"value":143},"You start your app on a local port, for example ",{"type":59,"tag":145,"props":146,"children":147},"a",{"href":32},[148],{"type":59,"tag":80,"props":149,"children":151},{"className":150},[],[152],{"type":68,"value":31},{"type":68,"value":154},".",{"type":59,"tag":139,"props":156,"children":157},{},[158,160,165],{"type":68,"value":159},"You run the tunnel agent. It makes an ",{"type":59,"tag":64,"props":161,"children":162},{},[163],{"type":68,"value":164},"outbound",{"type":68,"value":166}," TLS connection to the tunnel service. Outbound connections pass through NAT and most firewalls, so nothing inbound needs to be opened.",{"type":59,"tag":139,"props":168,"children":169},{},[170],{"type":68,"value":171},"The service assigns a public hostname and terminates HTTPS for it.",{"type":59,"tag":139,"props":173,"children":174},{},[175],{"type":68,"value":176},"A browser or API client requests the public URL. The service forwards the request over the open connection.",{"type":59,"tag":139,"props":178,"children":179},{},[180,182,187],{"type":68,"value":181},"The agent sends it to ",{"type":59,"tag":80,"props":183,"children":185},{"className":184},[],[186],{"type":68,"value":31},{"type":68,"value":188},", takes the response and streams it back the same way.",{"type":59,"tag":60,"props":190,"children":191},{},[192,194,199],{"type":68,"value":193},"In effect, the tunnel service is a ",{"type":59,"tag":145,"props":195,"children":196},{"href":27},[197],{"type":68,"value":198},"reverse proxy",{"type":68,"value":200}," on the internet whose upstream happens to be your laptop.",{"type":59,"tag":73,"props":202,"children":204},{"code":203},"browser ──HTTPS──▶ tunnel service (public hostname, TLS)\n                        ▲\n                        │ outbound connection opened by the agent\n                        │\nyour machine:      tunnel agent ──▶ http://localhost:3000\n",[205],{"type":59,"tag":80,"props":206,"children":207},{"__ignoreMap":7},[208],{"type":68,"value":203},{"type":59,"tag":128,"props":210,"children":212},{"id":211},"what-tunnels-are-used-for",[213],{"type":68,"value":214},"What tunnels are used for",{"type":59,"tag":216,"props":217,"children":218},"ul",{},[219,229,239,256,266,276],{"type":59,"tag":139,"props":220,"children":221},{},[222,227],{"type":59,"tag":64,"props":223,"children":224},{},[225],{"type":68,"value":226},"Showing work in progress",{"type":68,"value":228}," to a client or teammate without deploying.",{"type":59,"tag":139,"props":230,"children":231},{},[232,237],{"type":59,"tag":64,"props":233,"children":234},{},[235],{"type":68,"value":236},"Testing on real devices.",{"type":68,"value":238}," Open your local site on a phone over HTTPS.",{"type":59,"tag":139,"props":240,"children":241},{},[242,247,249,255],{"type":59,"tag":64,"props":243,"children":244},{},[245],{"type":68,"value":246},"Callbacks and OAuth redirects",{"type":68,"value":248}," that need a public HTTPS URL. See ",{"type":59,"tag":145,"props":250,"children":252},{"href":251},"/glossary/callback-url/",[253],{"type":68,"value":254},"callback URL",{"type":68,"value":154},{"type":59,"tag":139,"props":257,"children":258},{},[259,264],{"type":59,"tag":64,"props":260,"children":261},{},[262],{"type":68,"value":263},"Receiving webhooks",{"type":68,"value":265}," during development.",{"type":59,"tag":139,"props":267,"children":268},{},[269,274],{"type":59,"tag":64,"props":270,"children":271},{},[272],{"type":68,"value":273},"Reaching a service with no public IP",{"type":68,"value":275},", such as a home server, a Raspberry Pi or an internal dashboard.",{"type":59,"tag":139,"props":277,"children":278},{},[279,284],{"type":59,"tag":64,"props":280,"children":281},{},[282],{"type":68,"value":283},"TCP services",{"type":68,"value":285}," like SSH or a database, with a TCP tunnel instead of HTTP.",{"type":59,"tag":128,"props":287,"children":289},{"id":288},"tunnel-vs-webhook-forwarding",[290],{"type":68,"value":291},"Tunnel vs webhook forwarding",{"type":59,"tag":60,"props":293,"children":294},{},[295,297,302],{"type":68,"value":296},"For webhooks specifically, ",{"type":59,"tag":145,"props":298,"children":299},{"href":29},[300],{"type":68,"value":301},"webhook forwarding",{"type":68,"value":303}," is often the better tool:",{"type":59,"tag":305,"props":306,"children":307},"table",{},[308,328],{"type":59,"tag":309,"props":310,"children":311},"thead",{},[312],{"type":59,"tag":313,"props":314,"children":315},"tr",{},[316,320,324],{"type":59,"tag":317,"props":318,"children":319},"th",{},[],{"type":59,"tag":317,"props":321,"children":322},{},[323],{"type":68,"value":11},{"type":59,"tag":317,"props":325,"children":326},{},[327],{"type":68,"value":23},{"type":59,"tag":329,"props":330,"children":331},"tbody",{},[332,351,385,403],{"type":59,"tag":313,"props":333,"children":334},{},[335,341,346],{"type":59,"tag":336,"props":337,"children":338},"td",{},[339],{"type":68,"value":340},"Exposes",{"type":59,"tag":336,"props":342,"children":343},{},[344],{"type":68,"value":345},"The whole port, any path",{"type":59,"tag":336,"props":347,"children":348},{},[349],{"type":68,"value":350},"Only webhook requests to one URL",{"type":59,"tag":313,"props":352,"children":353},{},[354,359,380],{"type":59,"tag":336,"props":355,"children":356},{},[357],{"type":68,"value":358},"Machine offline",{"type":59,"tag":336,"props":360,"children":361},{},[362,364,370,372,378],{"type":68,"value":363},"Requests fail (usually a ",{"type":59,"tag":145,"props":365,"children":367},{"href":366},"/http-errors/502-bad-gateway/",[368],{"type":68,"value":369},"502",{"type":68,"value":371}," or ",{"type":59,"tag":145,"props":373,"children":375},{"href":374},"/http-errors/504-gateway-timeout/",[376],{"type":68,"value":377},"504",{"type":68,"value":379},")",{"type":59,"tag":336,"props":381,"children":382},{},[383],{"type":68,"value":384},"Requests are stored and retried or replayed",{"type":59,"tag":313,"props":386,"children":387},{},[388,393,398],{"type":59,"tag":336,"props":389,"children":390},{},[391],{"type":68,"value":392},"One URL, many destinations",{"type":59,"tag":336,"props":394,"children":395},{},[396],{"type":68,"value":397},"No",{"type":59,"tag":336,"props":399,"children":400},{},[401],{"type":68,"value":402},"Yes",{"type":59,"tag":313,"props":404,"children":405},{},[406,411,415],{"type":59,"tag":336,"props":407,"children":408},{},[409],{"type":68,"value":410},"Interactive browsing",{"type":59,"tag":336,"props":412,"children":413},{},[414],{"type":68,"value":402},{"type":59,"tag":336,"props":416,"children":417},{},[418],{"type":68,"value":397},{"type":59,"tag":60,"props":420,"children":421},{},[422,424,430,432,438],{"type":68,"value":423},"Webhook Relay offers both: ",{"type":59,"tag":80,"props":425,"children":427},{"className":426},[],[428],{"type":68,"value":429},"relay connect",{"type":68,"value":431}," for tunnels and ",{"type":59,"tag":80,"props":433,"children":435},{"className":434},[],[436],{"type":68,"value":437},"relay forward",{"type":68,"value":439}," for webhook forwarding.",{"type":59,"tag":128,"props":441,"children":443},{"id":442},"common-pitfalls",[444],{"type":68,"value":445},"Common pitfalls",{"type":59,"tag":216,"props":447,"children":448},{},[449,467,500,535,552,562],{"type":59,"tag":139,"props":450,"children":451},{},[452,457,459,465],{"type":59,"tag":64,"props":453,"children":454},{},[455],{"type":68,"value":456},"The URL changes on every restart.",{"type":68,"value":458}," Many free tunnel tools assign a random hostname each time, so every webhook or OAuth setting must be updated. Use a tool that keeps a stable name. With Webhook Relay, reusing ",{"type":59,"tag":80,"props":460,"children":462},{"className":461},[],[463],{"type":68,"value":464},"--name",{"type":68,"value":466}," keeps the same address.",{"type":59,"tag":139,"props":468,"children":469},{},[470,475,477,483,485,491,493,499],{"type":59,"tag":64,"props":471,"children":472},{},[473],{"type":68,"value":474},"Host header mismatches.",{"type":68,"value":476}," Dev servers like Vite and webpack reject requests whose ",{"type":59,"tag":80,"props":478,"children":480},{"className":479},[],[481],{"type":68,"value":482},"Host",{"type":68,"value":484}," is not ",{"type":59,"tag":80,"props":486,"children":488},{"className":487},[],[489],{"type":68,"value":490},"localhost",{"type":68,"value":492}," (\"Invalid Host header\" or \"Blocked request\"). Allow the tunnel hostname in the dev server config, or have the tunnel ",{"type":59,"tag":145,"props":494,"children":496},{"href":495},"/features/rewrite-host-header/",[497],{"type":68,"value":498},"rewrite the Host header",{"type":68,"value":154},{"type":59,"tag":139,"props":501,"children":502},{},[503,508,510,516,518,524,526,534],{"type":59,"tag":64,"props":504,"children":505},{},[506],{"type":68,"value":507},"Certificate errors.",{"type":68,"value":509}," If the tunnel forwards to ",{"type":59,"tag":80,"props":511,"children":513},{"className":512},[],[514],{"type":68,"value":515},"https://localhost",{"type":68,"value":517}," with a self-signed certificate, the agent may refuse it. Point the tunnel at the plain ",{"type":59,"tag":80,"props":519,"children":521},{"className":520},[],[522],{"type":68,"value":523},"http://",{"type":68,"value":525}," port and let the service terminate public TLS. A self-signed certificate exposed directly to browsers causes ",{"type":59,"tag":145,"props":527,"children":528},{"href":38},[529],{"type":59,"tag":80,"props":530,"children":532},{"className":531},[],[533],{"type":68,"value":37},{"type":68,"value":154},{"type":59,"tag":139,"props":536,"children":537},{},[538,543,545,550],{"type":59,"tag":64,"props":539,"children":540},{},[541],{"type":68,"value":542},"Binding to the wrong interface.",{"type":68,"value":544}," If the agent runs in Docker, ",{"type":59,"tag":80,"props":546,"children":548},{"className":547},[],[549],{"type":68,"value":490},{"type":68,"value":551}," means the container. Use the host's address or the service name.",{"type":59,"tag":139,"props":553,"children":554},{},[555,560],{"type":59,"tag":64,"props":556,"children":557},{},[558],{"type":68,"value":559},"Leaving it open.",{"type":68,"value":561}," A tunnel to a dev server with debug endpoints is a public attack surface. Add authentication and stop it when you're done.",{"type":59,"tag":139,"props":563,"children":564},{},[565,570,572,578],{"type":59,"tag":64,"props":566,"children":567},{},[568],{"type":68,"value":569},"Latency.",{"type":68,"value":571}," Traffic goes via the tunnel service. Pick the region closest to your users; Webhook Relay tunnels run in ",{"type":59,"tag":145,"props":573,"children":575},{"href":574},"/docs/tunnels/regions/",[576],{"type":68,"value":577},"multiple regions",{"type":68,"value":154},{"type":59,"tag":60,"props":580,"children":581},{},[582,584,589,591,597],{"type":68,"value":583},"See ",{"type":59,"tag":145,"props":585,"children":586},{"href":20},[587],{"type":68,"value":588},"tunnels",{"type":68,"value":590}," for setup, or ",{"type":59,"tag":145,"props":592,"children":594},{"href":593},"/docs/tunnels/demoing-your-website/",[595],{"type":68,"value":596},"demoing your website",{"type":68,"value":598}," for a walkthrough.",{"type":59,"tag":600,"props":601,"children":602},"style",{},[603],{"type":68,"value":604},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":7,"searchDepth":606,"depth":606,"links":607},3,[608,609,610,611],{"id":130,"depth":119,"text":133},{"id":211,"depth":119,"text":214},{"id":288,"depth":119,"text":291},{"id":442,"depth":119,"text":445},"markdown","content:glossary:tunnel.md","content","glossary/tunnel.md","glossary/tunnel","md",{"loc":4},1793199807644]