Autostart Tailscale on NixOS system boot & rebuild

Feb 23, 2025    #nixos   #tailscale   #homelab  

Are you tired of running sudo tailscale up every time you login, I know I am. So I thought why spend under two seconds waiting for something to run and using auto complete with ZSH to easily find the command when I can easily create a service that launches Tailscale on boot for me and re-launches on every rebuild of the system.

This configuration will ensure that:

This assumes you already have a Tailscale account and sops setup, if you don’t please see guides below.

Generating The Tailscale Auth Key:

Login to your account and click “Settings” –> “Personal Settings” –> “Keys” –> “Generate Auth Key”

Key settings, give your key a memorable name and also set it for the amount of time you want it to be valid for, I have set it for the max 90 days have added a reminder to my todo list to regenerate one in 85 days.

You should now have a Tailscale auth key.

Adding Tailscale Auth Key To Sops:

It’s now time to add our Tailscale Auth Key to sops so our system has access to it.

# If your sops file is called something different obviously you need to enter that...
sops secrets.yaml

# Store it in this format is important as we will be calling it later as a variable
tailscale_preauth: |
    TAILSCALE_AUTH_KEY=tskey-auth-xxxxx-xxxxxxxxxxxxx

Edit your configuration.nix and add the secret so it’s accessible by other services.

  sops = {
    defaultSopsFile = ./packages/sops/secrets.yaml;
    age.keyFile = "/home/martin/.config/sops/age/keys.txt";
    # I have other sops secerts here but have removed for brevity
    secrets.tailscale_preauth = { };
  };

Creating The Tailscale Service:


# Add tailscale to your system packages
environment.systemPackages = [ pkgs.tailscale ];

# Enable the tailscale service
services.tailscale.enable = true;

# Create a oneshot to autoconnect on rebuild/switch
systemd.services.tailscale-autoconnect = {
  description = "Automatic connection to Tailscale";

  after = [ "network-pre.target" "tailscale.service" ];
  wants = [ "network-pre.target" "tailscale.service" ];
  wantedBy = [ "multi-user.target" ];

  serviceConfig = {
    Type = "oneshot";

    # Pass our tailscale auth key from sops as a Environmental Variable
    EnvironmentFile = config.sops.secrets.tailscale_preauth.path;
  };

  # have the job run this shell script
  script = with pkgs; ''
    # wait for tailscaled to settle
    sleep 2

    # check if we are already authenticated to tailscale
    status="$(${tailscale}/bin/tailscale status -json | ${jq}/bin/jq -r .BackendState)"
    if [ $status = "Running" ]; then # if so, then do nothing
      exit 0
    fi

    # otherwise authenticate with tailscale using the key from secrets
    ${tailscale}/bin/tailscale up -authkey "$TAILSCALE_AUTH_KEY" --accept-routes=true
  '';
};

Breaking Down The Configuration:

Let’s break down what each part of this configuration does:

  1. System Package and Service Setup:

    • environment.systemPackages = [ pkgs.tailscale ] adds the Tailscale package to your system
    • services.tailscale.enable = true enables the Tailscale daemon service
  2. Automatic Connection Service: The systemd.services.tailscale-autoconnect section creates a systemd service that:

    • Runs once during system startup (Type = "oneshot")
    • Starts after networking and the Tailscale daemon are ready
    • Loads your authentication key from the sops-encrypted file
      • ${tailscale}/bin/tailscale up -authkey "$TAILSCALE_AUTH_KEY" --accept-routes=true
  3. Connection Script Logic: The script section:

    1. Waits 2 seconds for the Tailscale daemon to fully start
    2. Checks if you’re already connected to Tailscale by querying its status
    3. If already connected (Running state), exits without doing anything
    4. If not connected, authenticates using your stored auth key
    5. Enables route acceptance with --accept-routes=true
      • +Note+: I also use some of my nodes as routers within my home network to allow access to other hosts so I also pass the --accept-routes=true argument, however if you don’t do this you can omit this argument.

Rebuilding The System:

That’s it now run either:

Check Tailscale is running & connected