SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

Run Tailscale commands without sudo

Set the Tailscale operator user once, then run serve and funnel from a deploy account without sudo. What the setting changes, and what still needs root.

Run Tailscale commands without sudo

To run Tailscale commands without sudo, tell the daemon which Unix user is allowed to control it. That user is called the operator. You name it once, with sudo, and the point of the command is to stop needing sudo afterwards.

sudo tailscale set --operator=$USER

After that, the same user can run tailscale up, tailscale set, tailscale serve and tailscale funnel with no password prompt. Check that the value was stored:

tailscale debug prefs

The output is JSON, and one field in it is OperatorUser. If it holds your username, the setting is live. If it is empty, nothing was stored, and the section below on machines that are not logged in explains why.

What the operator setting actually does

The daemon, tailscaled, listens on a Unix socket. Every tailscale command is a small client that connects to that socket and calls a local HTTP API. The socket is the only way in, so the socket is where the permission decision happens.

ls -l /run/tailscale/tailscaled.sock

The mode reads srw-rw-rw-, which is 0666. Any local user may open it. The file mode is not the check. On Linux, tailscaled asks the kernel for the peer credentials of each connection, meaning the user ID of the process on the other end, and decides from that UID (user ID) what the connection may do.

A connection gets read access simply by arriving over that socket. It gets write access only if the UID is 0 (root), or the UID tailscaled itself runs as, or the UID of the operator user. Every other UID stays read-only.

That split explains the behaviour people find strange. tailscale status is a read, so any local user can already run it without sudo. tailscale set --advertise-exit-node is a write, so a non-operator user is refused even though the command looks similar.

There is no group fallback on Linux. macOS treats members of the admin group as operators, and the Linux build has no equivalent group check, so adding a user to the sudo group gives that user no access to the daemon at all. Naming the operator is the only way to grant write access without becoming root.

The daemon logs the decision it made for every connection. Watch it while you run a command in another session:

sudo journalctl -u tailscaled -f

You will see lines naming the UID and the verdict, such as connection from userid 1000; is configured operator or connection from userid 1001; read-only. That log is the fastest way to tell a permission problem from a network problem, because a refused command always produces one of those lines.

Why the setting survives a restart

The operator is a preference, not a daemon flag. It is not stored in /etc/default/tailscaled, and it is not part of the systemd unit. It lives in the daemon's own state file.

sudo tailscale debug statedir

The packaged unit starts the daemon with --state=/var/lib/tailscale/tailscaled.state, and that directory is mode 0700, owned by root. So the setting survives systemctl restart tailscaled, a package upgrade and a reboot. You set it once per machine, not once per login session.

One thing does clear it: preferences are stored per profile. Each account the machine has logged in to has its own preference set, listed by tailscale switch --list. Switch profiles, or log out and log in to a different tailnet, and the new profile starts with no operator. Set it again after that login.

Set the operator on a machine that is not logged in yet

On a machine with no active login, sudo tailscale set --operator=$USER can fail or fail to stick, because there is no profile to store the preference in. The error names the part of the API that refused:

Access denied: profiles access denied

Use 'sudo tailscale login'.
To not require root, use 'sudo tailscale set --operator=$USER' once.

Do both jobs in one command instead. tailscale up takes the same flag, so the login and the operator are written together:

sudo tailscale up --operator=$USER

That prints a login URL, and the preference is saved with the profile the login creates. One warning about $USER: your shell expands it before sudo runs, so it is normally your own login name. Inside a root shell, started by sudo -i or sudo su, the value is root, and you would be naming root as the operator, which grants nothing new. Run echo $USER first if you are not sure, or type the username out.

Check which user is the operator right now

The stored value is in the preferences, readable by anyone on the box:

tailscale debug prefs

A stronger check is to make the write attempt as the account you care about. Writing the value the daemon already holds changes nothing, so it is safe to repeat:

sudo -u deploy tailscale set --operator=deploy

Success prints nothing and exits 0. Failure prints an Access denied block, which means the daemon did not match that user's UID against its stored operator. Test as the account, not as yourself, because the check is on the UID of the calling process.

Script serve and funnel from a deploy user

This is the common reason to want the setting. A deploy user that publishes an application over Tailscale needs write access to the daemon and nothing else on the box.

tailscale serve --bg --https=443 http://127.0.0.1:8080
tailscale serve status

Port 443 appears there with no root and no setcap step, because the listener belongs to tailscaled, which already runs as root. The deploy user asks the daemon to listen, and the daemon listens. The privileged port is never bound inside the deploy user's own process. The same applies to TLS (transport layer security): the daemon fetches and renews the node's certificate itself, so serve needs no certificate files in the deploy user's home directory. For the rest of the syntax, see the tailscale serve command cheat sheet, and if you are choosing between the two commands, serve keeps the service inside your tailnet while funnel publishes it to the public internet.

In a deploy script, add --yes to any serve or funnel command that would otherwise stop and ask for confirmation. A script that blocks on a prompt looks exactly like a script that hangs.

Funnel has a second gate that the operator setting does not open. Publishing to the internet also requires your tailnet policy to allow funnel on that node, which is an admin console setting. A funnel command that is refused with a message about tailnet policy is a policy problem, not a local permission problem, and no amount of sudo fixes it.

Status checks from a monitoring account

Do not make your monitoring user the operator. Monitoring reads, and reads are already allowed for every local user:

sudo -u monitoring tailscale status --json

That works today on a stock install, with no operator set and no sudo rule for the monitoring account. A check that only reads needs no privilege, so granting one would add risk and buy nothing. This is worth remembering when you wire Tailscale health into a small monitoring agent on the same server.

What still needs root after you set the operator

The operator setting covers the daemon's API. It does not touch the rest of the system, so these still need root:

  • systemctl start, stop or restart on the tailscaled service, and anything else that manages the unit.
  • Editing /etc/default/tailscaled, which is where daemon flags and environment variables live.
  • Installing or upgrading the package, including the repository setup that comes with it. If that step is where you are stuck, the install error guide for Ubuntu covers the usual causes.
  • Reading /var/lib/tailscale, mode 0700, which holds the node key and every profile's preferences.
  • tailscale cert, which writes a certificate and its private key to disk.

That last one is governed by a separate switch. Certificate fetching is allowed for one non-root user, named by the TS_PERMIT_CERT_UID environment variable in the daemon's defaults file:

echo 'TS_PERMIT_CERT_UID=caddy' | sudo tee -a /etc/default/tailscaled
sudo systemctl restart tailscaled

Being the operator does not grant this, and being permitted for certificates does not make a user the operator. They are two independent settings, and a web server such as Caddy usually needs the certificate one rather than the operator one.

The security trade off, stated plainly

The operator user controls this machine's membership in your tailnet. That is a real privilege, so decide it deliberately. An operator can run tailscale logout or tailscale down and take the machine off the tailnet. An operator can run tailscale up --advertise-routes=... or --advertise-exit-node and offer this machine's network path to other nodes, subject to approval in the admin console. An operator can run tailscale funnel and expose a local port to the public internet where tailnet policy permits it.

The sharpest edge is Tailscale SSH. An operator can run tailscale set --ssh=true, which enables the daemon's SSH server. That server runs as root, and your tailnet access rules decide which tailnet users may connect and which local user they land as. If those rules allow a root login, an operator who turns SSH on has opened a path to root on the machine. Read your policy before you grant the setting, and understand what the coordination server does and does not control before you rely on it.

So give the operator role to one service account, not to every human with a shell. Keep that account's other powers narrow and build it with least privilege from the start. Note also that the operator's actions leave no entry in the sudo log, because no sudo call happened. They appear in the daemon journal instead, so keep a record of what that account runs if you need an audit trail.

Failure modes, with the strings you will see

Access denied, with a suggestion that is always the same. The CLI prints this whenever the daemon refuses a write and you are not root:

Access denied: prefs access denied

Use 'sudo tailscale set --advertise-exit-node'.
To not require root, use 'sudo tailscale set --operator=$USER' once.

Read the first line, not the suggestion. It names the API that refused: prefs access denied for a preference write, profiles access denied for a login or a profile switch. The suggested command in the middle line is built from whatever you typed, so it is not a diagnosis.

Unknown user. A username that does not exist on the machine is rejected when the daemon looks up its UID, with a message of the form error looking up operator 'deploy' uid: user: unknown user deploy. Confirm the account exists first with getent passwd deploy.

The account exists, but only in a directory service. Accounts that live in LDAP or Active Directory and not in /etc/passwd can fail that same lookup, because the official binaries resolve users without going through NSS (name service switch). A local service account avoids the problem entirely, and a local account is what you want for a deploy user anyway.

It worked last week and not today. Check whether the machine was logged out, or moved to another account with tailscale switch. Preferences are per profile, so a new profile arrives with no operator and every write starts failing again for the same user.

The setting is there but the user still cannot write. Confirm you are testing with the right UID. sudo -u deploy tailscale ... runs as deploy, while a plain tailscale ... from your own shell runs as you. The journal line connection from userid N; read-only tells you which UID the daemon actually saw, and that number is the one to compare against id -u deploy.

FAQ

Do I need sudo to run tailscale status?

No. On Linux, the daemon's socket is open to every local user and read requests are always permitted, so tailscale status and tailscale debug prefs work from any account with no sudo and no operator setting. Only writes, such as tailscale up, tailscale set, tailscale serve and tailscale funnel, need root or the operator user. If a monitoring account only reads, leave it as it is.

Does the Tailscale operator setting survive a reboot?

Yes. The operator is stored with the rest of the daemon's preferences in /var/lib/tailscale/tailscaled.state, so it survives a service restart, a package upgrade and a reboot. The one case where it disappears is a profile change: preferences are per logged-in account, so logging out, logging in to a different tailnet, or running tailscale switch gives you a profile with no operator set. Set it again after any of those.

Is it safe to make my deploy user the Tailscale operator?

It is a real grant of power over that machine's place in the tailnet, so treat it as one. The operator can take the node off the tailnet, advertise routes or an exit node for approval, publish a port with funnel where policy allows, and enable Tailscale SSH. That last point matters most: the SSH server runs as root, and if your access rules permit a root login, the operator has a route to root. Give the role to a single service account, and check your tailnet access rules before you do.

How do I remove the operator user again?

Run sudo tailscale set --operator= with nothing after the equals sign, then confirm with tailscale debug prefs that OperatorUser is empty. If your version does not accept the empty value, set the operator to root instead. Root already has write access through its UID, so naming root as operator revokes the other account without leaving a gap. Either way the change takes effect on the next connection, and the user's next write attempt returns the Access denied block.