Tailscale Funnel examples

With Tailscale Funnel, you can expose local services, individual folders, or even plain text to the public internet over HTTPS. We’ve heard from lots of Tailscale users about how they’re using Funnel, and we have collected these examples to help inspire you to use Funnel in new and interesting ways.

If you want to share a demo of a web application with a client, you can do that with Funnel. If you want to quickly test changes to a webhook receiver without waiting for the cloud to converge on every change, you can do that with Funnel. If you want to expose the development copy of your website to the world to test your OpenGraph metadata changes, you can do that with Funnel.

Conceptually, if you have an HTTP or TCP service listening on your local machine, you can use the Tailscale client to expose it to the internet with Funnel.

Due to macOS app sandbox limitations, serving files and directories with Funnel is limited to Tailscale’s open source variant. If you’ve installed Tailscale on macOS through the Mac App Store or as a standalone System Extension, you can use Funnel to share ports but not files or directories.

Before you begin trying out the examples in this topic, we recommend you review the setup information for Funnel.

Share a simple file server

In this example, we will explore how to use the tailscale funnel command to create a simple file server. Using Funnel as a file server is often much more efficient than transferring through a third-party service and more convenient than using something like Python’s http.server.

To see how we can go about this, we first need some files to serve. For this example, we’ll create those from scratch, but feel free to use existing files on your local machine that you would like to share:

$ mkdir /tmp/public
$ echo "Hello World" > /tmp/public/hello.txt
$ echo "Pangolin" > /tmp/public/animal.txt

Now, we can serve these files over the internet using Funnel:

$ sudo tailscale funnel /tmp/public

On Windows, instead of using sudo, open an Administrator console by pressing Windows+x then selecting Terminal (Admin) from the menu. Then run:

c:\Users\Amelie> tailscale funnel c:\tmp\public

The status will confirm the previous command and provide the hostname that Funnel configures for this node. You can see that this URL is available over the internet:

$ sudo tailscale funnel /tmp/public
Available on the internet:
https://amelie-workstation.pango-lin.ts.net

|-- / path /tmp/public

Press Ctrl+C to exit.

For the purpose of this guide, we will use curl to confirm the URLs work, but you should also be able to see the two files we created by visiting your Funnel URL in a browser. You can see that we get a directory listing when we request the /public folder:

$ curl -L https://amelie-workstation.pango-lin.ts.net
<pre>
<a href="animal.txt">animal.txt</a>
<a href="hello.txt">hello.txt</a>
</pre>

$ curl -L https://amelie-workstation.pango-lin.ts.net/animal.txt
Pangolin

Serve a static site

This example shows you how to serve a static HTML website to the internet.

To get started, let’s create some files as an example of what a static site would consist of: an index file and some assets. If you have existing website assets on your machine, you can use those instead. Hand-coded HTML and CSS might be all you need, or you can use one of the many static site generators that exist.

/tmp/static-site/index.html

<html>
  <head>
    <title>Hello World</title>
    <link rel="stylesheet" href="/styles.css" />
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>

/tmp/static-site/styles.css

*,
html {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: monospace;
  font-size: 10vw;
  text-transform: uppercase;
}

body {
  position: absolute;
  left: 50%;
  top: 25%;
  transform: translate3d(-50%, -50%, 0);
  overflow: hidden;
}

h1 {
  position: relative;
  top: 2em;
  animation: slide-up 3s infinite;
}

@keyframes slide-up {
  0% {
    top: 2em;
  }
  50% {
    top: 0em;
  }
  100% {
    top: 2em;
  }
}

Now, let’s serve our static site to the internet:

$ sudo tailscale funnel /tmp/static-site

The status will confirm the previous command and provide the hostname that Funnel configures for this node. Here, we can see it’s available on the internet:

$ sudo tailscale funnel /tmp/static-site
Available on the internet:
https://amelie-workstation.pango-lin.ts.net

|-- / path /tmp/static-site

Press Ctrl+C to exit.

You can now open the URL in your browser to confirm everything is working.

A web page served over Tailscale as viewed in the browser.

Expose a development server to the public

Having a route accessible with Funnel means that other users on the internet can reach out to and interact with a local server running on your machine. For example, if you are working on a blog post and want to share a draft for review, you can make your development site available to the public with Funnel.

Assuming you have a local HTTP server running on port 3000, you can expose the local server to the internet over HTTPS with Funnel:

$ sudo tailscale funnel 3000

The status will confirm the previous command and provide the hostname that Funnel configures for this node. Here, we can see it’s available on the internet:

$ sudo tailscale funnel 3000
Available on the internet:
https://amelie-workstation.pango-lin.ts.net

|-- / proxy http://127.0.0.1:3000

Press Ctrl+C to exit.

Open the URL in your browser to confirm that everything is working correctly.

As long as your development machine is turned on and connected to Tailscale, the data will be routed to and from your development server with Funnel. Remember, Funnel uses relay servers to convey the encrypted data to and from your device. It does not allow devices on the internet to connect directly to your machine.

Share a Funnel node

One of the nice things about Funnel is having a predictable, stable DNS name, like amelie-workstation.pango-lin.ts.net. This allows you to set or share your DNS name once and have it easily accessible any time you turn your funnel on.

But what if you want to share the Funnel DNS name with multiple collaborators or colleagues? This can come up if you’re configuring a backend like GitHub with a development webhook URL and wish to keep that URL stable, no matter which developer is currently testing the webhook APIs.

Our recommendation for sharing a Funnel node is to set up a node with the desired name. Let’s call it github-hooks-dev.pango-lin.ts.net. Optionally, turn on Tailscale SSH to make it easier to connect to.

Turn on Funnel on this node, for example:

$ sudo tailscale funnel 8080

This enables Funnel to forward HTTPS traffic from any path to the machine’s http://localhost:8080.

Now for the magic! We’re one command away from being able to forward our webhook development server to this shared Funnel node, thus making it available over the Funnel we just set up.

From the other machines that you wish to share the Funnel with, start your development server for testing your webhooks. In the following example, we use port 3000 as the local development server’s port. Finally, start an SSH reverse proxy connecting your local development server to the shared Funnel node, configured on its port 8080, which we set up as our Funnel’s target. For example:

$ ssh -NT -R 8080:127.0.0.1:3000 github-hook-dev.pango-lin.ts.net

You should be able to test your webhook or visit the URL https://github-hook-dev.pango-lin.ts.net and see requests going from the internet through your shared Funnel node to your local development server!

Lastly, what if the shared Funnel is already in use? You’ll receive an error message when establishing the SSH reverse proxy. It will report a similar message to the following:

Warning: remote port forwarding failed for listen port 8080

Test webhook receiver changes

Having a route accessible with Funnel means that other services on the internet can reach out to it and submit data, such as webhooks from vendors like GitHub or Stripe. If you are working on a webhook receiver (such as with go-playground/webhooks), you will want to test it with a faster turnaround time than if you deployed every change to the cloud.

You can use Funnel to expose the webhook receiver on your development machine and then use that URL for the service you are integrating with.

Assuming you have a local HTTP server running on port 3000, set up Funnel as a reverse proxy to that server:

$ sudo tailscale funnel 3000
Available on the internet:
https://amelie-workstation.pango-lin.ts.net

|-- / proxy http://127.0.0.1:3000

Press Ctrl+C to exit.

Configure the other service to use your URL and webhook path as normal. You can trigger messages to your service like any other webhook receiver. As long as your development machine is turned on and connected to Tailscale, the webhooks will be routed to your development server with Funnel.