{"slug":"tailscale-serve-examples","title":"Tailscale Serve examples","tags":["tailscale"],"agent_summary":"Last validated: Jan 14, 2026","trigger_phrases":[],"runnable":false,"markdown":"\r\n# Tailscale Serve examples\r\n\r\nLast validated: Jan 14, 2026\r\n\r\n[Tailscale Serve](https://tailscale.com/docs/features/tailscale-serve) lets you share content and local servers from your Tailscale node to your Tailscale network (known as a tailnet). This topic 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've collected these examples to help inspire you to use it in new and interesting ways.\r\n\r\nDue to macOS app sandbox limitations, serving files and directories with Serve only works with Tailscale's [open source variant](https://tailscale.com/docs/concepts/macos-variants). If you've installed Tailscale on macOS through the Mac App Store or as a Standalone variant system extension, you can use Serve to share ports but not files or directories.\r\n\r\nAdditionally, keep in mind that [access control rules](https://tailscale.com/docs/features/access-control) apply to Serve just like any other service. If you have access control rules that restrict access to certain devices or users, those rules will also apply to the services you're sharing with Serve.\r\n\r\nBefore you begin trying out the examples in this topic, we recommend you review the [setup](https://tailscale.com/docs/features/tailscale-serve#get-started-with-serve) information for Serve.\r\n\r\n## [Share a basic file server](https://tailscale.com/docs/reference/examples/serve\\#share-a-basic-file-server)\r\n\r\nIn this example, we will explore how to use Serve to create a basic file server shared with other devices in your tailnet. Using the [`tailscale serve`](https://tailscale.com/docs/reference/tailscale-cli/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`.\r\n\r\nFor 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 device that you would like to share instead.\r\n\r\n```shell\r\nmkdir /tmp/public\r\necho \"Hello World\" > /tmp/public/hello.txt\r\necho \"Pangolin\" > /tmp/public/animal.txt\r\n```\r\n\r\nNow, we can serve these files to your tailnet using Serve:\r\n\r\n```shell\r\nsudo tailscale serve /tmp/public\r\nAvailable within your tailnet\r\nhttps://amelie-workstation.pango-lin.ts.net\r\n\r\n|-- / path /tmp/public\r\n\r\nPress Ctrl+C to exit.\r\n```\r\n\r\nOn Windows, instead of using `sudo`, open an Administrator console by pressing Windows+x then selecting **Terminal (Admin)** from the menu. Then run:\r\n\r\n```shell\r\nc:\\Users\\Amelie> tailscale serve c:\\tmp\\public\r\n```\r\n\r\nThroughout this topic, Windows users should run `tailscale serve` commands without `sudo` but in the Admin terminal.\r\n\r\nBy 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 using Serve even when the session ends, use the `--bg` flag:\r\n\r\n```shell\r\nsudo tailscale serve --bg /tmp/public\r\nAvailable within your tailnet:\r\nhttps://amelie-workstation.pango-lin.ts.net\r\n\r\n|-- / path  /tmp/public\r\n\r\nServe started and running in the background.\r\nTo disable the proxy, run: tailscale serve off\r\n```\r\n\r\nIf you run Serve in the background, you'll need to remember to turn Serve off with the command [`tailscale serve off`](https://tailscale.com/docs/reference/tailscale-cli/serve#disable-tailscale-serve), when you want to stop sharing to your tailnet.\r\n\r\nFor the purpose of this guide, we will use `curl` to confirm the URLs work, but you should also be able to access the two files we created by visiting your Serve URL in a browser:\r\n\r\n```shell\r\ncurl -L https://amelie-workstation.pango-lin.ts.net\r\n<pre>\r\n<a href=\"animal.txt\">animal.txt</a>\r\n<a href=\"hello.txt\">hello.txt</a>\r\n</pre>\r\n\r\ncurl -L https://amelie-workstation.pango-lin.ts.net/animal.txt\r\nPangolin\r\n```\r\n\r\n## [Serve a static site](https://tailscale.com/docs/reference/examples/serve\\#serve-a-static-site)\r\n\r\nThis example shows you how to serve a static HTML website to other devices in your tailnet.\r\n\r\nTo 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 device, 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](https://github.com/myles/awesome-static-generators).\r\n\r\n`/tmp/static-site/index.html`\r\n\r\n```html\r\n<html>\r\n  <head>\r\n    <title>Hello World</title>\r\n    <link rel=\"stylesheet\" href=\"/styles.css\" />\r\n  </head>\r\n  <body>\r\n    <h1>Hello World</h1>\r\n  </body>\r\n</html>\r\n```\r\n\r\n`/tmp/static-site/styles.css`\r\n\r\n```css\r\n*,\r\nhtml {\r\n  margin: 0;\r\n  padding: 0;\r\n  box-sizing: border-box;\r\n  font-family: monospace;\r\n  font-size: 10vw;\r\n  text-transform: uppercase;\r\n}\r\n\r\nbody {\r\n  position: absolute;\r\n  left: 50%;\r\n  top: 25%;\r\n  transform: translate3d(-50%, -50%, 0);\r\n  overflow: hidden;\r\n}\r\n\r\nh1 {\r\n  position: relative;\r\n  top: 2em;\r\n  animation: slide-up 3s infinite;\r\n}\r\n\r\n@keyframes slide-up {\r\n  0% {\r\n        top: 2em;\r\n  }\r\n  50% {\r\n    top: 0em;\r\n  }\r\n  100% {\r\n    top: 2em;\r\n  }\r\n}\r\n```\r\n\r\nNow, let's serve our static site to the tailnet:\r\n\r\n```shell\r\nsudo tailscale serve /tmp/static-site\r\n```\r\n\r\nThe status confirms the previous command and provides the hostname that Serve configures for this node. Here, we can verify it's in the tailnet only:\r\n\r\n```shell\r\nsudo tailscale serve /tmp/static-site\r\nAvailable within your tailnet:\r\nhttps://amelie-workstation.pango-lin.ts.net\r\n\r\n|-- / path /tmp/static-site\r\n\r\nPress Ctrl+C to exit.\r\n```\r\n\r\nYou can now open the URL in your browser to confirm everything is working.\r\n\r\n![A web page served over Tailscale as viewed in the browser.](https://tailscale.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fserve-hello-world.b88bb87e.png&w=1200&q=75)\r\n\r\n### [Expose a development server to your tailnet](https://tailscale.com/docs/reference/examples/serve\\#expose-a-development-server-to-your-tailnet)\r\n\r\nHaving a route accessible with Serve means that other users in your tailnet can reach out to and interact with a local server running on your device. 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.\r\n\r\nAssuming you have a local HTTP server running on port `3000`, you can expose the local server to your tailnet over HTTPS with Serve:\r\n\r\n```shell\r\ntailscale serve 3000\r\n```\r\n\r\n```shell\r\ntailscale serve 3000\r\nAvailable within your tailnet:\r\nhttps://amelie-workstation.pango-lin.ts.net\r\n\r\n|-- / proxy http://127.0.0.1:3000\r\n\r\nPress Ctrl+C to exit.\r\n```\r\n\r\nOpen the URL in your browser to confirm that everything is working correctly.\r\n\r\nAs long as your development device is online and connected to Tailscale, the data routes to and from your development server with Serve.\r\n\r\nWhen using Serve to forward ports, you don't need to use `sudo` or an Administrator console.\r\n\r\n## [Bind local services to your tailnet](https://tailscale.com/docs/reference/examples/serve\\#bind-local-services-to-your-tailnet)\r\n\r\nIn 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.\r\n\r\nHere's an example of rebinding your device's SSH server to port `2222`. You might find this helpful when using Tailscale SSH to provide backup access to your device's SSH server, for example:\r\n\r\n```shell\r\ntailscale serve --tcp 2222 22\r\n```\r\n\r\nFrom another device, connect as you normally would with SSH but add the port we configured as a flag to the command. For example:\r\n\r\n```shell\r\nssh -p 2222 <user>@100.x.y.z\r\n```\r\n\r\n## [Share a Serve node](https://tailscale.com/docs/reference/examples/serve\\#share-a-serve-node)\r\n\r\nOne of the nice things about Serve is having a predictable, stable DNS name, like `web-dev.pango-lin.ts.net`. This lets you set or share your DNS name once and have it accessible any time you turn Serve on.\r\n\r\nBut 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.\r\n\r\nOur 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.\r\n\r\nTurn on Serve on this node, for example:\r\n\r\n```shell\r\ntailscale serve 8080\r\n```\r\n\r\nThis enables Serve to forward HTTPS traffic from any path to the device's `http://localhost:8080`.\r\n\r\nWe're now 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.\r\n\r\nFrom the other devices 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. Then, 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:\r\n\r\n```shell\r\nssh -NT -R 8080:127.0.0.1:3000 web-dev.pango-lin.ts.net\r\n```\r\n\r\nWhat if the shared Serve is already in use? You'll receive an error message when establishing the SSH reverse proxy. It will report a message similar to the following:\r\n\r\n```shell\r\nWarning: remote port forwarding failed for listen port 8080\r\n```\r\n\r\n## [Forward app capabilities to a local service](https://tailscale.com/docs/reference/examples/serve\\#forward-app-capabilities-to-a-local-service)\r\n\r\nThis feature is available in Tailscale v1.92 or later.\r\n\r\nServe lets you use the `--accept-app-caps` command line flag to list [app capabilities](https://tailscale.com/docs/features/access-control/grants/grants-app-capabilities) Serve should forward to your local service.\r\nIf a user or tagged node that makes a request has been granted any of the app capabilities specified, Serve will convert them into serialised JSON and forward them in a header called `Tailscale-App-Capabilities`.\r\nThe destination server can use these headers, for example, to decide which resources the Tailscale user or tagged node associated with the request can access.\r\n\r\nSay you wanted Serve to forward an app capability called `example.com/cap/sql` to a destination server.\r\nLet's assume you had granted `example.com/cap/sql` to certain users or tagged nodes in your Tailnet's ACL like so:\r\n\r\n```shell\r\n\"grants\": [\\\r\n  {\\\r\n    \"src\": [\"group:eng\"],\\\r\n    \"dst\": [\"tag:sql\"],\\\r\n    \"app\": {\\\r\n      \"example.com/cap/sql\": [\\\r\n        {\"src\": [\"main\", \"self\"]}\\\r\n      ]\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"src\": [\"group:admin\"],\\\r\n    \"dst\": [\"tag:sql\"],\\\r\n    \"app\": {\\\r\n      \"example.com/cap/sql\": [\\\r\n        {\"src\": [\"*\"]}\\\r\n      ]\\\r\n    }\\\r\n  }\\\r\n]\r\n```\r\n\r\nTo let Serve forward the app capability `example.com/cap/sql` to a local service at port 8080, run:\r\n\r\n```shell\r\ntailscale serve --accept-app-caps=example.com/cap/sql 8080\r\n```\r\n\r\nWhenever a user with that app capability makes a request to the destination server through the Serve proxy you've just created, the destination server will receive the header `Tailscale-App-Capabilities` containing the app capability as a JSON string. The header will contain the value `{\"example.com/cap/sql\":[{\"src\":[\"main\",\"self\"]}]}` for a user in `group:eng`, and `{\"example.com/cap/sql\":[{\"src\":[\"*\"]}]}` for a user in `group:admin`.\r\n\r\nWhen you use the capability headers to authorize users or tagged nodes at a backend service, it's best practice to only have the service listen on localhost. Otherwise, any user that can call your service directly (rather than with the Serve URL) could trivially provide their own values for these HTTP headers. By listening only on localhost, this limits tampering to only other services running on the Serve device, and not anyone on your LAN or tailnet.\r\n\r\n![Project Logo](https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed)\r\n\r\nAsk AI\r\n\r\nreCAPTCHA\r\n\r\nRecaptcha requires verification.\r\n\r\nprotected by **reCAPTCHA**\r\n","html":"<h1>Tailscale Serve examples</h1>\n<p>Last validated: Jan 14, 2026</p>\n<p><a href=\"https://tailscale.com/docs/features/tailscale-serve\">Tailscale Serve</a> lets you share content and local servers from your Tailscale node to your Tailscale network (known as a tailnet). This topic 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've collected these examples to help inspire you to use it in new and interesting ways.</p>\n<p>Due to macOS app sandbox limitations, serving files and directories with Serve only works with Tailscale's <a href=\"https://tailscale.com/docs/concepts/macos-variants\">open source variant</a>. If you've installed Tailscale on macOS through the Mac App Store or as a Standalone variant system extension, you can use Serve to share ports but not files or directories.</p>\n<p>Additionally, keep in mind that <a href=\"https://tailscale.com/docs/features/access-control\">access control rules</a> apply to Serve just like any other service. If you have access control rules that restrict access to certain devices or users, those rules will also apply to the services you're sharing with Serve.</p>\n<p>Before you begin trying out the examples in this topic, we recommend you review the <a href=\"https://tailscale.com/docs/features/tailscale-serve#get-started-with-serve\">setup</a> information for Serve.</p>\n<h2><a href=\"https://tailscale.com/docs/reference/examples/serve#share-a-basic-file-server\">Share a basic file server</a></h2>\n<p>In this example, we will explore how to use Serve to create a basic file server shared with other devices in your tailnet. Using the <a href=\"https://tailscale.com/docs/reference/tailscale-cli/serve\"><code>tailscale serve</code></a> 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 <code>http.server</code>.</p>\n<p>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 device that you would like to share instead.</p>\n<pre><code class=\"language-shell\">mkdir /tmp/public\r\necho \"Hello World\" > /tmp/public/hello.txt\r\necho \"Pangolin\" > /tmp/public/animal.txt\n</code></pre>\n<p>Now, we can serve these files to your tailnet using Serve:</p>\n<pre><code class=\"language-shell\">sudo tailscale serve /tmp/public\r\nAvailable within your tailnet\r\nhttps://amelie-workstation.pango-lin.ts.net\r\n\r\n|-- / path /tmp/public\r\n\r\nPress Ctrl+C to exit.\n</code></pre>\n<p>On Windows, instead of using <code>sudo</code>, open an Administrator console by pressing Windows+x then selecting <strong>Terminal (Admin)</strong> from the menu. Then run:</p>\n<pre><code class=\"language-shell\">c:\\Users\\Amelie> tailscale serve c:\\tmp\\public\n</code></pre>\n<p>Throughout this topic, Windows users should run <code>tailscale serve</code> commands without <code>sudo</code> but in the Admin terminal.</p>\n<p>By default, Serve runs in the foreground, meaning that if you press <code>Ctrl+C</code> or close the terminal session, Tailscale will stop sharing over Serve. If you want to persist sharing using Serve even when the session ends, use the <code>--bg</code> flag:</p>\n<pre><code class=\"language-shell\">sudo tailscale serve --bg /tmp/public\r\nAvailable within your tailnet:\r\nhttps://amelie-workstation.pango-lin.ts.net\r\n\r\n|-- / path  /tmp/public\r\n\r\nServe started and running in the background.\r\nTo disable the proxy, run: tailscale serve off\n</code></pre>\n<p>If you run Serve in the background, you'll need to remember to turn Serve off with the command <a href=\"https://tailscale.com/docs/reference/tailscale-cli/serve#disable-tailscale-serve\"><code>tailscale serve off</code></a>, when you want to stop sharing to your tailnet.</p>\n<p>For the purpose of this guide, we will use <code>curl</code> to confirm the URLs work, but you should also be able to access the two files we created by visiting your Serve URL in a browser:</p>\n<pre><code class=\"language-shell\">curl -L https://amelie-workstation.pango-lin.ts.net\r\n&#x3C;pre>\r\n&#x3C;a href=\"animal.txt\">animal.txt&#x3C;/a>\r\n&#x3C;a href=\"hello.txt\">hello.txt&#x3C;/a>\r\n&#x3C;/pre>\r\n\r\ncurl -L https://amelie-workstation.pango-lin.ts.net/animal.txt\r\nPangolin\n</code></pre>\n<h2><a href=\"https://tailscale.com/docs/reference/examples/serve#serve-a-static-site\">Serve a static site</a></h2>\n<p>This example shows you how to serve a static HTML website to other devices in your tailnet.</p>\n<p>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 device, you can use those instead. Hand-coded HTML and CSS might be all you need, or you can use one of the <a href=\"https://github.com/myles/awesome-static-generators\">many static site generators that exist</a>.</p>\n<p><code>/tmp/static-site/index.html</code></p>\n<pre><code class=\"language-html\">&#x3C;html>\r\n  &#x3C;head>\r\n    &#x3C;title>Hello World&#x3C;/title>\r\n    &#x3C;link rel=\"stylesheet\" href=\"/styles.css\" />\r\n  &#x3C;/head>\r\n  &#x3C;body>\r\n    &#x3C;h1>Hello World&#x3C;/h1>\r\n  &#x3C;/body>\r\n&#x3C;/html>\n</code></pre>\n<p><code>/tmp/static-site/styles.css</code></p>\n<pre><code class=\"language-css\">*,\r\nhtml {\r\n  margin: 0;\r\n  padding: 0;\r\n  box-sizing: border-box;\r\n  font-family: monospace;\r\n  font-size: 10vw;\r\n  text-transform: uppercase;\r\n}\r\n\r\nbody {\r\n  position: absolute;\r\n  left: 50%;\r\n  top: 25%;\r\n  transform: translate3d(-50%, -50%, 0);\r\n  overflow: hidden;\r\n}\r\n\r\nh1 {\r\n  position: relative;\r\n  top: 2em;\r\n  animation: slide-up 3s infinite;\r\n}\r\n\r\n@keyframes slide-up {\r\n  0% {\r\n        top: 2em;\r\n  }\r\n  50% {\r\n    top: 0em;\r\n  }\r\n  100% {\r\n    top: 2em;\r\n  }\r\n}\n</code></pre>\n<p>Now, let's serve our static site to the tailnet:</p>\n<pre><code class=\"language-shell\">sudo tailscale serve /tmp/static-site\n</code></pre>\n<p>The status confirms the previous command and provides the hostname that Serve configures for this node. Here, we can verify it's in the tailnet only:</p>\n<pre><code class=\"language-shell\">sudo tailscale serve /tmp/static-site\r\nAvailable within your tailnet:\r\nhttps://amelie-workstation.pango-lin.ts.net\r\n\r\n|-- / path /tmp/static-site\r\n\r\nPress Ctrl+C to exit.\n</code></pre>\n<p>You can now open the URL in your browser to confirm everything is working.</p>\n<p><img src=\"https://tailscale.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fserve-hello-world.b88bb87e.png&#x26;w=1200&#x26;q=75\" alt=\"A web page served over Tailscale as viewed in the browser.\"></p>\n<h3><a href=\"https://tailscale.com/docs/reference/examples/serve#expose-a-development-server-to-your-tailnet\">Expose a development server to your tailnet</a></h3>\n<p>Having a route accessible with Serve means that other users in your tailnet can reach out to and interact with a local server running on your device. 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.</p>\n<p>Assuming you have a local HTTP server running on port <code>3000</code>, you can expose the local server to your tailnet over HTTPS with Serve:</p>\n<pre><code class=\"language-shell\">tailscale serve 3000\n</code></pre>\n<pre><code class=\"language-shell\">tailscale serve 3000\r\nAvailable within your tailnet:\r\nhttps://amelie-workstation.pango-lin.ts.net\r\n\r\n|-- / proxy http://127.0.0.1:3000\r\n\r\nPress Ctrl+C to exit.\n</code></pre>\n<p>Open the URL in your browser to confirm that everything is working correctly.</p>\n<p>As long as your development device is online and connected to Tailscale, the data routes to and from your development server with Serve.</p>\n<p>When using Serve to forward ports, you don't need to use <code>sudo</code> or an Administrator console.</p>\n<h2><a href=\"https://tailscale.com/docs/reference/examples/serve#bind-local-services-to-your-tailnet\">Bind local services to your tailnet</a></h2>\n<p>In addition to running an HTTPS server, you can use the command <code>tailscale serve</code> to bind local TCP-based services to your Tailscale IP and make them available privately across your tailnet.</p>\n<p>Here's an example of rebinding your device's SSH server to port <code>2222</code>. You might find this helpful when using Tailscale SSH to provide backup access to your device's SSH server, for example:</p>\n<pre><code class=\"language-shell\">tailscale serve --tcp 2222 22\n</code></pre>\n<p>From another device, connect as you normally would with SSH but add the port we configured as a flag to the command. For example:</p>\n<pre><code class=\"language-shell\">ssh -p 2222 &#x3C;user>@100.x.y.z\n</code></pre>\n<h2><a href=\"https://tailscale.com/docs/reference/examples/serve#share-a-serve-node\">Share a Serve node</a></h2>\n<p>One of the nice things about Serve is having a predictable, stable DNS name, like <code>web-dev.pango-lin.ts.net</code>. This lets you set or share your DNS name once and have it accessible any time you turn Serve on.</p>\n<p>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.</p>\n<p>Our recommendation for sharing a Serve node is to set up a node with the desired name. Let's call it <code>web-dev.pango-lin.ts.net</code>. Optionally, turn on Tailscale SSH to make it easier to connect to.</p>\n<p>Turn on Serve on this node, for example:</p>\n<pre><code class=\"language-shell\">tailscale serve 8080\n</code></pre>\n<p>This enables Serve to forward HTTPS traffic from any path to the device's <code>http://localhost:8080</code>.</p>\n<p>We're now 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.</p>\n<p>From the other devices 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. Then, 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:</p>\n<pre><code class=\"language-shell\">ssh -NT -R 8080:127.0.0.1:3000 web-dev.pango-lin.ts.net\n</code></pre>\n<p>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 message similar to the following:</p>\n<pre><code class=\"language-shell\">Warning: remote port forwarding failed for listen port 8080\n</code></pre>\n<h2><a href=\"https://tailscale.com/docs/reference/examples/serve#forward-app-capabilities-to-a-local-service\">Forward app capabilities to a local service</a></h2>\n<p>This feature is available in Tailscale v1.92 or later.</p>\n<p>Serve lets you use the <code>--accept-app-caps</code> command line flag to list <a href=\"https://tailscale.com/docs/features/access-control/grants/grants-app-capabilities\">app capabilities</a> Serve should forward to your local service.\r\nIf a user or tagged node that makes a request has been granted any of the app capabilities specified, Serve will convert them into serialised JSON and forward them in a header called <code>Tailscale-App-Capabilities</code>.\r\nThe destination server can use these headers, for example, to decide which resources the Tailscale user or tagged node associated with the request can access.</p>\n<p>Say you wanted Serve to forward an app capability called <code>example.com/cap/sql</code> to a destination server.\r\nLet's assume you had granted <code>example.com/cap/sql</code> to certain users or tagged nodes in your Tailnet's ACL like so:</p>\n<pre><code class=\"language-shell\">\"grants\": [\\\r\n  {\\\r\n    \"src\": [\"group:eng\"],\\\r\n    \"dst\": [\"tag:sql\"],\\\r\n    \"app\": {\\\r\n      \"example.com/cap/sql\": [\\\r\n        {\"src\": [\"main\", \"self\"]}\\\r\n      ]\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"src\": [\"group:admin\"],\\\r\n    \"dst\": [\"tag:sql\"],\\\r\n    \"app\": {\\\r\n      \"example.com/cap/sql\": [\\\r\n        {\"src\": [\"*\"]}\\\r\n      ]\\\r\n    }\\\r\n  }\\\r\n]\n</code></pre>\n<p>To let Serve forward the app capability <code>example.com/cap/sql</code> to a local service at port 8080, run:</p>\n<pre><code class=\"language-shell\">tailscale serve --accept-app-caps=example.com/cap/sql 8080\n</code></pre>\n<p>Whenever a user with that app capability makes a request to the destination server through the Serve proxy you've just created, the destination server will receive the header <code>Tailscale-App-Capabilities</code> containing the app capability as a JSON string. The header will contain the value <code>{\"example.com/cap/sql\":[{\"src\":[\"main\",\"self\"]}]}</code> for a user in <code>group:eng</code>, and <code>{\"example.com/cap/sql\":[{\"src\":[\"*\"]}]}</code> for a user in <code>group:admin</code>.</p>\n<p>When you use the capability headers to authorize users or tagged nodes at a backend service, it's best practice to only have the service listen on localhost. Otherwise, any user that can call your service directly (rather than with the Serve URL) could trivially provide their own values for these HTTP headers. By listening only on localhost, this limits tampering to only other services running on the Serve device, and not anyone on your LAN or tailnet.</p>\n<p><img src=\"https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed\" alt=\"Project Logo\"></p>\n<p>Ask AI</p>\n<p>reCAPTCHA</p>\n<p>Recaptcha requires verification.</p>\n<p>protected by <strong>reCAPTCHA</strong></p>\n"}