Tailscale Serve examples

Tailscale Serve is a powerful way to share local ports, files, directories, and even plain text with other devices on your Tailscale network (known as a tailnet). This article provides some guidance on using the most popular Serve features. We’ve heard from lots of Tailscale users about how they’re using Serve, and we have collected these examples to help inspire you to use it in new and interesting ways.

Due to macOS app sandbox limitations, serving files and directories with Serve 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 Serve 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 Serve.

Share a simple file server

In this example, we will explore how to use Serve to create a simple file server that is shared with other devices in your tailnet. Using the tailscale serve CLI command 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.

For the purposes of this guide, we need some files to serve. We’ll create those from scratch, but feel free to use existing files on your local machine that you would like to share instead.

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

Now, we can serve these files to your tailnet using Serve:

$ sudo tailscale serve /tmp/public
Available within your tailnet
https://amelie-workstation.pango-lin.ts.net

|-- / path /tmp/public

Press Ctrl+C to exit.

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 serve c:\tmp\public

Throughout this article, Windows users should run tailscale serve commands without sudo but in the Admin terminal.

By default, Serve runs in the foreground, meaning that if you press Ctrl+C or close the terminal session, Tailscale will stop sharing over Serve. If you want to persist sharing via Serve even when the session ends, use the --bg flag:

$ sudo tailscale serve --bg /tmp/public
Available within your tailnet:
https://amelie-workstation.pango-lin.ts.net

|-- / path  /tmp/public

Serve started and running in the background.
To disable the proxy, run: tailscale serve off

If you run Serve in the background, you’ll need to remember to turn Serve off with the command tailscale serve off, when you want to stop sharing to your tailnet.

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 Serve URL in a browser:

$ 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 other devices in your tailnet.

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 tailnet:

$ sudo tailscale serve /tmp/static-site

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

$ sudo tailscale serve /tmp/static-site
Available within your tailnet:
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 your tailnet

Having a route accessible with Serve means that other users on your tailnet 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 your tailnet with Serve.

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

tailscale serve 3000
$ tailscale serve 3000
Available within your tailnet:
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 Serve.

When using Serve to forward ports, you don’t need to use sudo or an Administrator console.

Bind local services to your tailnet

In addition to running an HTTPS server, you can use the command tailscale serve to bind local TCP-based services to your Tailscale IP and make them available privately across your tailnet.

Here’s an example of rebinding your machine’s SSH server to port 2222. You might find this helpful when using Tailscale SSH to provide backup access to your machine’s SSH server, for example:

$ tailscale serve --tcp 2222 22

From another machine, connect as you normally would via SSH but add the port we configured as a flag to the command. For example:

$ ssh -p 2222 <user>@100.x.y.z

Share a Serve node

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

But what if you want to share the Serve DNS name with multiple collaborators or colleagues? This can come up if you’re collaborating on developing a web application and wish to keep that URL stable, no matter which developer is currently working on the app.

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

Turn on Serve on this node, for example:

$ tailscale serve 8080

This enables Serve 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 web development server to this shared Serve node, thus making it available over the Serve we just set up.

From the other machines that you wish to share the Serve with, start your development server for testing your web app. 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 Serve node, configured on its port 8080, which we set up as our Serve’s target, for example:

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

Lastly, what if the shared Serve 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