<!doctype html><htmllang=enclass=no-js><head><metacharset=utf-8><metaname=viewportcontent="width=device-width,initial-scale=1"><metaname=descriptioncontent="An open source, self-hosted implementation of the Tailscale control server."><metaname=authorcontent="Headscale authors"><linkhref=https://juanfont.github.io/headscale/development/ref/routes/rel=canonical><linkhref=../oidc/rel=prev><linkhref=../tls/rel=next><linkrel=iconhref=../../assets/favicon.png><metaname=generatorcontent="mkdocs-1.6.1, mkdocs-material-9.6.12"><title>Routes - Headscale</title><linkrel=stylesheethref=../../assets/stylesheets/main.2afb09e1.min.css><linkrel=stylesheethref=../../assets/stylesheets/palette.06af60db.min.css><linkrel=preconnecthref=https://fonts.gstatic.comcrossorigin><linkrel=stylesheethref="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback"><style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style><script>__md_scope=newURL("../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script><metaproperty=og:typecontent=website><metaproperty=og:titlecontent="Routes - Headscale"><metaproperty=og:descriptioncontent="An open source, self-hosted implementation of the Tailscale control server."><metaproperty=og:imagecontent=https://juanfont.github.io/headscale/development/assets/images/social/ref/routes.png><metaproperty=og:image:typecontent=image/png><metaproperty=og:image:widthcontent=1200><metaproperty=og:image:heightcontent=630><metacontent=https://juanfont.github.io/headscale/development/ref/routes/property=og:url><metaname=twitter:cardcontent=summary_large_image><metaname=twitter:titlecontent="Routes - Headscale"><metaname=twitter:descriptioncontent="An open source, self-hosted implementation of the Tailscale control server."><metaname=twitter:imagecontent=https://juanfont.github.io/headscale/development/assets/images/social/ref/routes.png></head><bodydir=ltrdata-md-color-scheme=defaultdata-md-color-primary=whitedata-md-color-accent=indigo><inputclass=md-toggledata-md-toggle=drawertype=checkboxid=__drawerautocomplete=off><inputclass=md-toggledata-md-toggle=searchtype=checkboxid=__searchautocomplete=off><labelclass=md-overlayfor=__drawer></label><divdata-md-component=skip><ahref=#routesclass=md-skip> Skip to content </a></div><divdata-md-component=announce></div><divdata-md-color-scheme=defaultdata-md-component=outdatedhidden></div><headerclass=md-headerdata-md-component=header><navclass="md-header__inner md-grid"aria-label=Header><ahref=../..title=Headscaleclass="md-header__button md-logo"aria-label=Headscaledata-md-component=logo><imgsrc=../../logo/headscale3-dots.svgalt=logo></a><labelclass="md-header__button md-icon"for=__drawer><svgxmlns=http://www.w3.org/2000/svgviewbox="0 0 24 24"><pathd="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg></label><divclass=md-header__titledata-md-component=header-title><divclass=md-header__ellipsis><divclass=md-header__topic><spanclass=md-ellipsis> Headscale </span></div><divclass=md-header__topicdata-md-component=header-topic><spanclass=md-ellipsis> Routes </span></div></div></div><formclass=md-header__optiondata-md-component=palette><inputclass=md-optiondata-md-color-mediadata-md-color-scheme=defaultdata-md-color-primary=whitedata-md-color-accent=indigoaria-label="Switch to dark mode"type=radioname=__paletteid=__palette_0><labelclass="md-header__button md-icon"title="Switch to dark mode"for=__palette_1hidden><svgxmlns=http://www.w3.org/2000/svgviewbox="0 0 24 24"><pathd="M128a44000-444400044440004-444000-4-4m010a66001-6-6660016-6660016666001-66m8-9.31V4h-4.69L12.698.694H4v4.69L.6912415.31V20h4.69L1223.
</span></code></pre></div><p>If the node is already registered, it can advertise new routes or update previously announced routes with:</p><divclass="language-console highlight"><pre><span></span><code><spanid=__span-1-1><aid=__codelineno-1-1name=__codelineno-1-1href=#__codelineno-1-1></a><spanclass=gp>$ </span>sudo<spanclass=w></span>tailscale<spanclass=w></span><spanclass=nb>set</span><spanclass=w></span>--advertise-routes<spanclass=o>=</span><spanclass=m>10</span>.0.0.0/8,192.168.0.0/24
</span></code></pre></div><p>Finally, <ahref=#enable-ip-forwarding>enable IP forwarding</a> to route traffic.</p><h4id=enable-the-subnet-router-on-the-control-server>Enable the subnet router on the control server<aclass=headerlinkhref=#enable-the-subnet-router-on-the-control-servertitle="Permanent link">¶</a></h4><p>The routes of a tailnet can be displayed with the <code>headscale nodes list-routes</code> command. A subnet router with the hostname <code>myrouter</code> announced the IPv4 networks <code>10.0.0.0/8</code> and <code>192.168.0.0/24</code>. Those need to be approved before they can be used.</p><divclass="language-console highlight"><pre><span></span><code><spanid=__span-2-1><aid=__codelineno-2-1name=__codelineno-2-1href=#__codelineno-2-1></a><spanclass=gp>$ </span>headscale<spanclass=w></span>nodes<spanclass=w></span>list-routes
</span><spanid=__span-2-2><aid=__codelineno-2-2name=__codelineno-2-2href=#__codelineno-2-2></a><spanclass=go>ID | Hostname | Approved | Available | Serving (Primary)</span>
</span></code></pre></div><p>Approve all desired routes of a subnet router by specifying them as comma separated list:</p><divclass="language-console highlight"><pre><span></span><code><spanid=__span-3-1><aid=__codelineno-3-1name=__codelineno-3-1href=#__codelineno-3-1></a><spanclass=gp>$ </span>headscale<spanclass=w></span>nodes<spanclass=w></span>approve-routes<spanclass=w></span>--identifier<spanclass=w></span><spanclass=m>1</span><spanclass=w></span>--routes<spanclass=w></span><spanclass=m>10</span>.0.0.0/8,192.168.0.0/24
</span></code></pre></div><p>The node <code>myrouter</code> can now route the IPv4 networks <code>10.0.0.0/8</code> and <code>192.168.0.0/24</code> for the tailnet.</p><divclass="language-console highlight"><pre><span></span><code><spanid=__span-4-1><aid=__codelineno-4-1name=__codelineno-4-1href=#__codelineno-4-1></a><spanclass=gp>$ </span>headscale<spanclass=w></span>nodes<spanclass=w></span>list-routes
</span><spanid=__span-4-2><aid=__codelineno-4-2name=__codelineno-4-2href=#__codelineno-4-2></a><spanclass=go>ID | Hostname | Approved | Available | Serving (Primary)</span>
</span></code></pre></div><h4id=use-the-subnet-router>Use the subnet router<aclass=headerlinkhref=#use-the-subnet-routertitle="Permanent link">¶</a></h4><p>To accept routes advertised by a subnet router on a node:</p><divclass="language-console highlight"><pre><span></span><code><spanid=__span-5-1><aid=__codelineno-5-1name=__codelineno-5-1href=#__codelineno-5-1></a><spanclass=gp>$ </span>sudo<spanclass=w></span>tailscale<spanclass=w></span><spanclass=nb>set</span><spanclass=w></span>--accept-routes
</span></code></pre></div><p>Please refer to the official <ahref=https://tailscale.com/kb/1019/subnets#use-your-subnet-routes-from-other-devices>Tailscale documentation</a> for how to use a subnet router on different operating systems.</p><h3id=restrict-the-use-of-a-subnet-router-with-acl>Restrict the use of a subnet router with ACL<aclass=headerlinkhref=#restrict-the-use-of-a-subnet-router-with-acltitle="Permanent link">¶</a></h3><p>The routes announced by subnet routers are available to the nodes in a tailnet. By default, without an ACL enabled, all nodes can accept and use such routes. Configure an ACL to explicitly manage who can use routes.</p><p>The ACL snippet below defines three hosts, a subnet router <code>router</code>, a regular node <code>node</code> and <code>service.example.net</code> as internal service that can be reached via a route on the subnet router <code>router</code>. It allows the node <code>node</code> to access <code>service.example.net</code> on port 80 and 443 which is reachable via the subnet router. Access to the subnet router itself is denied.</p><divclass="language-json highlight"><spanclass=filename>Access the routes of a subnet router without the subnet router itself</span><pre><span></span><code><spanid=__span-6-1><aid=__codelineno-6-1name=__codelineno-6-1href=#__codelineno-6-1></a><spanclass=p>{</span>
</span><spanid=__span-6-3><aid=__codelineno-6-3name=__codelineno-6-3href=#__codelineno-6-3></a><spanclass=w></span><spanclass=c1>// the router is not referenced but announces 192.168.0.0/24"</span>
</span></code></pre></div><h3id=automatically-approve-routes-of-a-subnet-router>Automatically approve routes of a subnet router<aclass=headerlinkhref=#automatically-approve-routes-of-a-subnet-routertitle="Permanent link">¶</a></h3><p>The initial setup of a subnet router usually requires manual approval of their announced routes on the control server before they can be used by a node in a tailnet. Headscale supports the <code>autoApprovers</code> section of an ACL to automate the approval of routes served with a subnet router.</p><p>The ACL snippet below defines the tag <code>tag:router</code> owned by the user <code>alice</code>. This tag is used for <code>routes</code> in the <code>autoApprovers</code> section. The IPv4 route <code>192.168.0.0/24</code> is automatically approved once announced by a subnet router owned by the user <code>alice</code> and that also advertises the tag <code>tag:router</code>.</p><divclass="language-json highlight"><spanclass=filename>Subnet routers owned by alice and tagged with tag:router are automatically approved</span><pre><span></span><code><spanid=__span-7-1><aid=__codelineno-7-1name=__codelineno-7-1href=#__codelineno-7-1></a><spanclass=p>{</span>
</span><spanid=__span-7-15><aid=__codelineno-7-15name=__codelineno-7-15href=#__codelineno-7-15></a><spanclass=w></span><spanclass=c1>// more rules</span>
</span></code></pre></div><p>Advertise the route <code>192.168.0.0/24</code> from a subnet router that also advertises the tag <code>tag:router</code> when joining the tailnet:</p><divclass="language-console highlight"><pre><span></span><code><spanid=__span-8-1><aid=__codelineno-8-1name=__codelineno-8-1href=#__codelineno-8-1></a><spanclass=gp>$ </span>sudo<spanclass=w></span>tailscale<spanclass=w></span>up<spanclass=w></span>--login-server<spanclass=w></span><YOUR_HEADSCALE_URL><spanclass=w></span>--advertise-tags<spanclass=w></span>tag:router<spanclass=w></span>--advertise-routes<spanclass=w></span><spanclass=m>192</span>.168.0.0/24
</span></code></pre></div><p>Please see the <ahref=https://tailscale.com/kb/1337/acl-syntax#autoapprovers>official Tailscale documentation</a> for more information on auto approvers.</p><h2id=exit-node>Exit node<aclass=headerlinkhref=#exit-nodetitle="Permanent link">¶</a></h2><p>The setup of an exit node requires double opt-in, once from an exit node and once on the control server to allow its use within the tailnet. Optionally, use <ahref=#automatically-approve-an-exit-node-with-auto-approvers><code>autoApprovers</code> to automatically approve an exit node</a>.</p><h3id=setup-an-exit-node>Setup an exit node<aclass=headerlinkhref=#setup-an-exit-nodetitle="Permanent link">¶</a></h3><h4id=configure-a-node-as-exit-node>Configure a node as exit node<aclass=headerlinkhref=#configure-a-node-as-exit-nodetitle="Permanent link">¶</a></h4><p>Register a node and make it advertise itself as an exit node:</p><divclass="language-console highlight"><pre><span></span><code><spanid=__span-9-1><aid=__codelineno-9-1name=__codelineno-9-1href=#__codelineno-9-1></a><spanclass=gp>$ </span>sudo<spanclass=w></span>tailscale<spanclass=w></span>up<spanclass=w></span>--login-server<spanclass=w></span><YOUR_HEADSCALE_URL><spanclass=w></span>--advertise-exit-node
</span></code></pre></div><p>If the node is already registered, it can advertise exit capabilities like this:</p><divclass="language-console highlight"><pre><span></span><code><spanid=__span-10-1><aid=__codelineno-10-1name=__codelineno-10-1href=#__codelineno-10-1></a><spanclass=gp>$ </span>sudo<spanclass=w></span>tailscale<spanclass=w></span><spanclass=nb>set</span><spanclass=w></span>--advertise-exit-node
</span></code></pre></div><p>Finally, <ahref=#enable-ip-forwarding>enable IP forwarding</a> to route traffic.</p><h4id=enable-the-exit-node-on-the-control-server>Enable the exit node on the control server<aclass=headerlinkhref=#enable-the-exit-node-on-the-control-servertitle="Permanent link">¶</a></h4><p>The routes of a tailnet can be displayed with the <code>headscale nodes list-routes</code> command. An exit node can be recognized by its announced routes: <code>0.0.0.0/0</code> for IPv4 and <code>::/0</code> for IPv6. The exit node with the hostname <code>myexit</code> is already available, but needs to be approved:</p><divclass="language-console highlight"><pre><span></span><code><spanid=__span-11-1><aid=__codelineno-11-1name=__codelineno-11-1href=#__codelineno-11-1></a><spanclass=gp>$ </span>headscale<spanclass=w></span>nodes<spanclass=w></span>list-routes
</span><spanid=__span-11-2><aid=__codelineno-11-2name=__codelineno-11-2href=#__codelineno-11-2></a><spanclass=go>ID | Hostname | Approved | Available | Serving (Primary)</span>
</span></code></pre></div><p>For exit nodes, it is sufficient to approve either the IPv4 or IPv6 route. The other will be approved automatically.</p><divclass="language-console highlight"><pre><span></span><code><spanid=__span-12-1><aid=__codelineno-12-1name=__codelineno-12-1href=#__codelineno-12-1></a><spanclass=gp>$ </span>headscale<spanclass=w></span>nodes<spanclass=w></span>approve-routes<spanclass=w></span>--identifier<spanclass=w></span><spanclass=m>1</span><spanclass=w></span>--routes<spanclass=w></span><spanclass=m>0</span>.0.0.0/0
</span></code></pre></div><p>The node <code>myexit</code> is now approved as exit node for the tailnet:</p><divclass="language-console highlight"><pre><span></span><code><spanid=__span-13-1><aid=__codelineno-13-1name=__codelineno-13-1href=#__codelineno-13-1></a><spanclass=gp>$ </span>headscale<spanclass=w></span>nodes<spanclass=w></span>list-routes
</span><spanid=__span-13-2><aid=__codelineno-13-2name=__codelineno-13-2href=#__codelineno-13-2></a><spanclass=go>ID | Hostname | Approved | Available | Serving (Primary)</span>
</span></code></pre></div><h4id=use-the-exit-node>Use the exit node<aclass=headerlinkhref=#use-the-exit-nodetitle="Permanent link">¶</a></h4><p>The exit node can now be used on a node with:</p><divclass="language-console highlight"><pre><span></span><code><spanid=__span-14-1><aid=__codelineno-14-1name=__codelineno-14-1href=#__codelineno-14-1></a><spanclass=gp>$ </span>sudo<spanclass=w></span>tailscale<spanclass=w></span><spanclass=nb>set</span><spanclass=w></span>--exit-node<spanclass=w></span>myexit
</span></code></pre></div><p>Please refer to the official <ahref=https://tailscale.com/kb/1103/exit-nodes#use-the-exit-node>Tailscale documentation</a> for how to use an exit node on different operating systems.</p><h3id=restrict-the-use-of-an-exit-node-with-acl>Restrict the use of an exit node with ACL<aclass=headerlinkhref=#restrict-the-use-of-an-exit-node-with-acltitle="Permanent link">¶</a></h3><p>An exit node is offered to all nodes in a tailnet. By default, without an ACL enabled, all nodes in a tailnet can select and use an exit node. Configure <code>autogroup:internet</code> in an ACL rule to restrict who can use <em>any</em> of the available exit nodes.</p><divclass="language-json highlight"><spanclass=filename>Example use of autogroup:internet</span><pre><span></span><code><spanid=__span-15-1><aid=__codelineno-15-1name=__codelineno-15-1href=#__codelineno-15-1></a><spanclass=p>{</span>
</span></code></pre></div><h3id=automatically-approve-an-exit-node-with-auto-approvers>Automatically approve an exit node with auto approvers<aclass=headerlinkhref=#automatically-approve-an-exit-node-with-auto-approverstitle="Permanent link">¶</a></h3><p>The initial setup of an exit node usually requires manual approval on the control server before it can be used by a node in a tailnet. Headscale supports the <code>autoApprovers</code> section of an ACL to automate the approval of a new exit node as soon as it joins the tailnet.</p><p>The ACL snippet below defines the tag <code>tag:exit</code> owned by the user <code>alice</code>. This tag is used for <code>exitNode</code> in the <code>autoApprovers</code> section. A new exit node which is owned by the user <code>alice</code> and that also advertises the tag <code>tag:exit</code> is automatically approved:</p><divclass="language-json highlight"><spanclass=filename>Exit nodes owned by alice and tagged with tag:exit are automatically approved</span><pre><span></span><code><spanid=__span-16-1><aid=__codelineno-16-1name=__codelineno-16-1href=#__codelineno-16-1></a><spanclass=p>{</span>
</span><spanid=__span-16-13><aid=__codelineno-16-13name=__codelineno-16-13href=#__codelineno-16-13></a><spanclass=w></span><spanclass=c1>// more rules</span>
</span></code></pre></div><p>Advertise a node as exit node and also advertise the tag <code>tag:exit</code> when joining the tailnet:</p><divclass="language-console highlight"><pre><span></span><code><spanid=__span-17-1><aid=__codelineno-17-1name=__codelineno-17-1href=#__codelineno-17-1></a><spanclass=gp>$ </span>sudo<spanclass=w></span>tailscale<spanclass=w></span>up<spanclass=w></span>--login-server<spanclass=w></span><YOUR_HEADSCALE_URL><spanclass=w></span>--advertise-tags<spanclass=w></span>tag:exit<spanclass=w></span>--advertise-exit-node