Tailscale Service Not Initializing Network Interface on Fresh Boot

Hi everyone,

I recently installed Tailscale using the system package options, and for the most part, everything is working smoothly. However, I’ve encountered an issue that I’d like to get some feedback on.

After a fresh boot, the Tailscale service (tailscaled) does not create the network interface or assign its host IP address unless I manually log into the system. This behavior seems inconsistent with the expectation that the service should initialize automatically during the boot process.

To investigate, I checked the dependencies for the multi-user.target target, and it appears that the tailscaled.service is correctly listed as a dependency:

OS: NixOS 24.05
Tailscale version: 1.80.3

systemctl list-dependencies multi-user.target | rg tailscaled
● ├─tailscaled.service

...

systemctl status tailscaled.service                          
● tailscaled.service - Tailscale node agent
     Loaded: loaded (/etc/systemd/system/tailscaled.service; enabled; preset: ignored)

Despite this, the service doesn’t seem to function as intended until after I log in. Has anyone else experienced this issue? If so, I’d appreciate any advice or suggestions on how to resolve it.

These are the relevant parts of how I’m configuring it:

    services = {
      tailscale = {
        enable = true;
        openFirewall = true;
        extraUpFlags = [ "--ssh" ];
      };
      tailscaleAuth = {
        enable = false;
        group = "users";
        user = "cig0";
      };
    };

Thank you in advance for your help!

Ref: I found a link that I thought would be related to my issue, but it turns out OP was setting Talescale up manually: [solved] Possible to automatically authenticate Tailscale after every rebuild/reboot?

Edit: I confirm the Tailscale service isn’t running on a fresh boot without logging into my user accounts, as the host is down in the Tailscale web dashboard. Once I log in, the host automatically gets the Connected legend and green dot in the dashboard.

The issue is that tailscaled.service takes its time to create the virtual network interface and get the IP from Tailscale.

This issue was a deal-breaker for me because I wanted to bind the Tailscale IP address to OpenSSH, and OpenSSH was complaining it couldn’t bind to the IP address because (of a race condition) it didn’t exist.

I eventually found a solution (which I truly dislike), but it’s the only way I found to make sshd.service wait until tailscaled.service finishes its business:

  systemd.services = {
      sshd = lib.mkIf config.mySystem.services.tailscale.enable {
        /*
           HACK_ Add an ExecStartPre to ensure Tailscale interface is ready.

          I absolutely hate that this is the only way I've found to force `sshd.service` to wait for
          `tailscaled.service` to create the network interface and assign the IP address.
        */
        serviceConfig.ExecStartPre = [
          "${pkgs.bash}/bin/bash -c 'until ${pkgs.iproute2}/bin/ip addr show dev tailscale0 | ${pkgs.gnugrep}/bin/grep -q \"${cfg.tsIpPerrrkele}\"; do sleep 1; done'"
        ];

        # TODO_ Clean up unnecessary services and target dependencies
        after = [
          "network-online.target"
          "nm-file-secret-agent.service"
          "tailscaled.service"
          "late-multi-user.target"
        ];
        requires = [
          "network-online.target"
          "nm-file-secret-agent.service"
        ];
        wants = [
          "tailscaled.service"
        ];
      };
    };

    /*
      FIXME_ I'm leaving this option here to revisit this approach in the future

      systemd.sockets.sshd = lib.mkIf config.mySystem.services.tailscale.enable {
        # This ensures the socket will correctly bind once the interface is available
        bindsTo = [ "sys-subsystem-net-devices-tailscale0.device" ];
        after = [
          # "network-online.target"
          "sys-subsystem-net-devices-tailscale0.device"
        ];
      };
    */