<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dimitar Apostolov</title>
    <description>The latest articles on DEV Community by Dimitar Apostolov (@pehota).</description>
    <link>https://dev.to/pehota</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F630261%2Fc1d3f2d0-eb63-464a-acf3-f518fd6795aa.jpeg</url>
      <title>DEV Community: Dimitar Apostolov</title>
      <link>https://dev.to/pehota</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pehota"/>
    <language>en</language>
    <item>
      <title>Stop Memorising IPs: Domain Names for Your Proxmox Homelab with Tailscale App Connectors</title>
      <dc:creator>Dimitar Apostolov</dc:creator>
      <pubDate>Thu, 23 Jul 2026 06:26:27 +0000</pubDate>
      <link>https://dev.to/pehota/stop-memorising-ips-domain-names-for-your-proxmox-homelab-with-tailscale-app-connectors-5bk7</link>
      <guid>https://dev.to/pehota/stop-memorising-ips-domain-names-for-your-proxmox-homelab-with-tailscale-app-connectors-5bk7</guid>
      <description>&lt;p&gt;I already had Tailscale running — one LXC configured as a subnet router, exposing the rest of the containers over the tailnet. So I could reach them remotely. I just still had to remember the IPs.&lt;/p&gt;

&lt;p&gt;I wanted &lt;code&gt;https://proxmox.home.lab:8006&lt;/code&gt;. I wanted to type a name, not a number.&lt;/p&gt;

&lt;p&gt;Tailscale App Connectors turned out to be the answer. Getting there took longer than it should have, so here's what actually works.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You'll End Up With
&lt;/h2&gt;

&lt;p&gt;One LXC doing three jobs: subnet router, app connector, and DNS server. Any device on your tailnet resolves &lt;code&gt;&amp;lt;service&amp;gt;.home.lab&lt;/code&gt; to the right container IP and routes traffic through automatically. App Connectors are available on &lt;a href="https://tailscale.com/pricing" rel="noopener noreferrer"&gt;all Tailscale plans&lt;/a&gt; — check the pricing page to confirm, as plans change.&lt;/p&gt;

&lt;p&gt;This guide assumes you already have a Tailscale LXC acting as a subnet router. If you don't, &lt;a href="https://www.youtube.com/watch?v=VVIC_QtHo1M&amp;amp;list=PLesIr7QQh4I4" rel="noopener noreferrer"&gt;this video&lt;/a&gt; walks through setting one up — then come back to Step 2 which adds the app connector role on top.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three values thread through the entire configuration.&lt;/strong&gt; If any one of them drifts between steps, something silently breaks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Where it must match&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tag name (e.g. &lt;code&gt;tag:app-connector&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;ACL tagOwners, ACL nodeAttrs, ACL autoApprovers, &lt;code&gt;tailscale up&lt;/code&gt; command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain suffix (e.g. &lt;code&gt;home.lab&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;ACL domains list, dnsmasq entries, Tailscale Apps page, split DNS restriction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LAN subnet (e.g. &lt;code&gt;192.168.1.0/24&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;ACL autoApprovers, &lt;code&gt;tailscale up&lt;/code&gt; command, dnsmasq IPs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Step 1: Update the Tailscale ACL Policy
&lt;/h2&gt;

&lt;p&gt;Go to the &lt;a href="https://login.tailscale.com/admin/acls" rel="noopener noreferrer"&gt;Tailscale admin console&lt;/a&gt; → &lt;strong&gt;Access Controls&lt;/strong&gt;. You'll see a huJSON editor with your current policy. Don't replace it — merge these sections in as top-level keys. If you already have an &lt;code&gt;grants&lt;/code&gt; block, add the new rule inside it rather than creating a second one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tagOwners"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tag:app-connector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"grants"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"dst"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"autoApprovers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"routes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"tag:app-connector"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"::/0"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"tag:app-connector"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"nodeAttrs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tailscale.com/app-connectors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[CHOOSE_NAME_FOR_THE_APP_CONNECTOR]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"connectors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"tag:app-connector"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"domains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;whatever&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;domains&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;need&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;expose&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="s2"&gt;"proxmox.home.lab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="s2"&gt;"homeassistant.home.lab"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; &lt;code&gt;target&lt;/code&gt; in &lt;code&gt;nodeAttrs&lt;/code&gt; must be &lt;code&gt;["*"]&lt;/code&gt;. I tried &lt;code&gt;["tag:app-connector"]&lt;/code&gt; first and it didn't work. Tailscale throws: &lt;code&gt;tailscale.com/app-connectors: can only be specified with target "*"&lt;/code&gt;. The &lt;code&gt;connectors&lt;/code&gt; field inside the app definition is where you control which node does the work. &lt;code&gt;target&lt;/code&gt; controls which nodes receive this configuration, and for app connectors it must be everyone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Configure the LXC as App Connector
&lt;/h2&gt;

&lt;p&gt;Before installing dnsmasq, check whether &lt;code&gt;systemd-resolved&lt;/code&gt; is holding port 53 — if it is, dnsmasq will fail to start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl is-active systemd-resolved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If active, disable its stub listener:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/#DNSStubListener=yes/DNSStubListener=no/'&lt;/span&gt; /etc/systemd/resolved.conf
systemctl restart systemd-resolved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now SSH into your Tailscale LXC and advertise its new roles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale up &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--advertise-connector&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--advertise-tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tag:app-connector &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--advertise-routes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.1.0/24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;192.168.1.0/24&lt;/code&gt; with your actual LAN subnet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; The node has to explicitly opt in with &lt;code&gt;--advertise-connector&lt;/code&gt;. I spent time looking for a button in the admin console to designate a node as a connector — there isn't one. The node advertises its own role via the CLI. Until you run this, nothing shows up as selectable in the Apps UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify it worked:&lt;/strong&gt; Go to Admin console → &lt;strong&gt;Machines&lt;/strong&gt;. Your LXC should now show an app connector badge next to it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Install and Configure dnsmasq
&lt;/h2&gt;

&lt;p&gt;App Connectors handle routing — but they don't resolve domain names to IPs. You need a local DNS server for that. dnsmasq on the same LXC is the simplest option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; dnsmasq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit &lt;code&gt;/etc/dnsmasq.conf&lt;/code&gt; and append:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# Without this, dnsmasq rejects queries from the Tailscale interface.
# The systemd unit passes --local-service which restricts to local subnets;
# adding interface=tailscale0 overrides that behaviour.
&lt;/span&gt;&lt;span class="n"&gt;interface&lt;/span&gt;=&lt;span class="n"&gt;tailscale0&lt;/span&gt;
&lt;span class="n"&gt;bind&lt;/span&gt;-&lt;span class="n"&gt;interfaces&lt;/span&gt;

&lt;span class="c"&gt;# One line per container — use your actual LAN IPs
&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;=/&lt;span class="n"&gt;proxmox&lt;/span&gt;.&lt;span class="n"&gt;home&lt;/span&gt;.&lt;span class="n"&gt;lab&lt;/span&gt;/&lt;span class="m"&gt;192&lt;/span&gt;.&lt;span class="m"&gt;168&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;address&lt;/span&gt;=/&lt;span class="n"&gt;homeassistant&lt;/span&gt;.&lt;span class="n"&gt;home&lt;/span&gt;.&lt;span class="n"&gt;lab&lt;/span&gt;/&lt;span class="m"&gt;192&lt;/span&gt;.&lt;span class="m"&gt;168&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Already using dnsmasq for DHCP or other purposes?&lt;/strong&gt; Just add the &lt;code&gt;interface=tailscale0&lt;/code&gt; line alongside your existing &lt;code&gt;interface=&lt;/code&gt; lines rather than replacing them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Start and verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnsmasq
systemctl status dnsmasq   &lt;span class="c"&gt;# should be active, no "limited to local subnets"&lt;/span&gt;

dig @127.0.0.1 proxmox.home.lab
&lt;span class="c"&gt;# ANSWER SECTION should show your container's IP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; After the first start, status may show &lt;code&gt;DNS service limited to local subnets&lt;/code&gt;. This comes from &lt;code&gt;--local-service&lt;/code&gt; baked into the systemd unit file — there's no config file option to override it. The fix is &lt;code&gt;interface=tailscale0&lt;/code&gt;, which tells dnsmasq to accept queries on the Tailscale interface. After restarting, the warning disappears.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Register the App in the Admin Console
&lt;/h2&gt;

&lt;p&gt;Admin console → &lt;strong&gt;&lt;a href="https://login.tailscale.com/admin/apps" rel="noopener noreferrer"&gt;Apps&lt;/a&gt;&lt;/strong&gt; → &lt;strong&gt;Add an app&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: anything descriptive&lt;/li&gt;
&lt;li&gt;Type: &lt;strong&gt;Custom&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Domains: your &lt;code&gt;home.lab&lt;/code&gt; domains, comma-separated (same ones from Step 1)&lt;/li&gt;
&lt;li&gt;Connector tag: &lt;code&gt;tag:app-connector&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Save&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll notice the domains appear in both the ACL &lt;code&gt;nodeAttrs&lt;/code&gt; and here. They serve different purposes: the ACL controls routing policy (which node handles which traffic), while the Apps UI registers the connector association Tailscale uses for DNS-over-HTTPS forwarding. Both need the same domains.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Add a Split DNS Nameserver
&lt;/h2&gt;

&lt;p&gt;Admin console → &lt;strong&gt;DNS&lt;/strong&gt; → &lt;strong&gt;Add nameserver&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nameserver: your LXC's tailnet IPv4 address — the &lt;code&gt;100.x.y.z&lt;/code&gt; one (run &lt;code&gt;tailscale ip -4&lt;/code&gt; on the LXC)&lt;/li&gt;
&lt;li&gt;Check &lt;strong&gt;Restrict to domain&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Domain: &lt;code&gt;home.lab&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Save&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also make sure &lt;strong&gt;Magic DNS&lt;/strong&gt; is toggled on on the same page. Split DNS doesn't work without it.&lt;/p&gt;

&lt;p&gt;This tells Tailscale: for anything under &lt;code&gt;home.lab&lt;/code&gt;, ask this specific nameserver. Everything else uses normal DNS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Configure Clients
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Linux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--accept-routes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;--accept-dns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;macOS with the GUI app:&lt;/strong&gt;&lt;br&gt;
Tailscale menu bar → Preferences → enable &lt;strong&gt;Use Tailscale DNS&lt;/strong&gt;. Done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;macOS CLI only (no GUI app):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; The Tailscale CLI on macOS cannot manage system DNS. &lt;code&gt;tailscale set --accept-dns=true&lt;/code&gt; accepts the flag silently but does nothing — the CLI has no mechanism to install the required Network Extension without the GUI app. &lt;code&gt;tailscale debug resolve your-domain.home.lab&lt;/code&gt; will return &lt;code&gt;no such host&lt;/code&gt; even though the netmap shows routes correctly configured.&lt;/p&gt;

&lt;p&gt;Workaround — point your Wi-Fi DNS manually at the LXC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;networksetup &lt;span class="nt"&gt;-setdnsservers&lt;/span&gt; Wi-Fi &amp;lt;LXC tailnet IPv4&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To revert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;networksetup &lt;span class="nt"&gt;-setdnsservers&lt;/span&gt; Wi-Fi &lt;span class="s2"&gt;"Empty"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Accessing Your Services
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://proxmox.home.lab:8006
http://homeassistant.home.lab:8123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;App Connectors don't do anything about ports — the browser defaults to 80. Include the port for anything that doesn't run on 80 or 443. If you want to drop ports entirely, put a reverse proxy (Caddy or nginx) in front of your services.&lt;/p&gt;




&lt;h2&gt;
  
  
  One More Gotcha: Chrome's DNS-over-HTTPS
&lt;/h2&gt;

&lt;p&gt;This one is cross-platform, not just macOS. Chrome has its own DoH that bypasses system DNS entirely. If &lt;code&gt;dig&lt;/code&gt; resolves correctly but Chrome says "page can't be reached", that's why.&lt;/p&gt;

&lt;p&gt;Disable it: Settings → Privacy and security → Security → &lt;strong&gt;Use secure DNS&lt;/strong&gt; → off.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding a New Container
&lt;/h2&gt;

&lt;p&gt;Three places, all using the exact same domain name:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;dnsmasq config&lt;/strong&gt; — add &lt;code&gt;address=/newservice.home.lab/&amp;lt;LAN IP&amp;gt;&lt;/code&gt; then &lt;code&gt;systemctl restart dnsmasq&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ACL nodeAttrs&lt;/strong&gt; — add &lt;code&gt;"newservice.home.lab"&lt;/code&gt; to the &lt;code&gt;domains&lt;/code&gt; list, save&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin console → Apps&lt;/strong&gt; — edit your homelab app, add the domain&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;can only be specified with target "*"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Change &lt;code&gt;"target": ["tag:app-connector"]&lt;/code&gt; to &lt;code&gt;"target": ["*"]&lt;/code&gt; in nodeAttrs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dnsmasq shows "limited to local subnets"&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;interface=tailscale0&lt;/code&gt; + &lt;code&gt;bind-interfaces&lt;/code&gt; to &lt;code&gt;/etc/dnsmasq.conf&lt;/code&gt;, restart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dnsmasq fails to start&lt;/td&gt;
&lt;td&gt;Port 53 conflict — disable systemd-resolved stub: &lt;code&gt;sed -i 's/#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf &amp;amp;&amp;amp; systemctl restart systemd-resolved&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;dig @&amp;lt;LXC IP&amp;gt; service.home.lab&lt;/code&gt; works but system DNS doesn't&lt;/td&gt;
&lt;td&gt;macOS CLI can't manage split DNS — use GUI app or &lt;code&gt;sudo networksetup -setdnsservers Wi-Fi &amp;lt;LXC tailnet IPv4&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chrome: "page can't be reached" despite &lt;code&gt;dig&lt;/code&gt; resolving&lt;/td&gt;
&lt;td&gt;Disable Chrome secure DNS: Settings → Privacy and security → Security → Use secure DNS → off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser: "page can't be reached" for any browser&lt;/td&gt;
&lt;td&gt;Missing port — try &lt;code&gt;https://service.home.lab:&amp;lt;port&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LXC not showing as app connector in admin console&lt;/td&gt;
&lt;td&gt;Run &lt;code&gt;tailscale up --advertise-connector --advertise-tags=tag:app-connector&lt;/code&gt; on the LXC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>homelab</category>
      <category>proxmox</category>
      <category>tailscale</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
