# Add a server

> Join servers, cloud VMs and other headless machines, with a sign-in link or with an auth key from a script.

There are two ways to add a machine nobody sits at:

| | Sign-in link | Auth key |
|---|---|---|
| Best for | A few servers you set up by hand | Scripts, images, CI and machines that come and go |
| Runs as | The installed background service | Your own systemd unit |
| Needs | A browser on **any** device | A key from the dashboard |

## With a sign-in link

The sign-in link doesn't have to be opened on the server itself: open it on your laptop.

1. On the server, install and connect:

   ```bash
   curl -fsSL https://coord.getunderpass.com/download/install.sh | sh
   ```

2. It prints a link:

   ```text
   To add this machine to your tailnet, open:

       https://app.getunderpass.com/…
   ```

   Open it on any device where you're signed in, and approve the machine.
3. The server prints `Connected as db-prod (100.64.0.21).` It stays connected through reboots, because the service starts at boot.

## With an auth key

An **auth key** lets a machine join on its own, with no browser.

> [!IMPORTANT]
> Auth keys work with the command-line tool running on its own, **not** through the background service the install command sets up. Use the steps below on machines where you haven't run the install command, or uninstall the service first (see [Install](/getting-started/install#uninstall)).

### 1. Create an auth key

In the dashboard, open **Auth keys** and create one. Choose:

- **Reusable** if several machines will use it; otherwise it works once.
- **Ephemeral** for short-lived machines like CI runners: they're removed after 10 minutes offline.
- **Pre-approved** so machines skip [device approval](/manage/machines#device-approval).
- **Tags**, like `tag:prod`, to give the machines a role in [access rules](/features/access-controls). Tagged machines don't expire.
- **Auth key expiration**: 1 to 90 days, 7 by default.

The key starts with `tskey-auth-` and is **shown once**. Keep it in your secret manager. See [Auth keys](/manage/auth-keys).

### 2. Download the command-line tool

```bash
ARCH=$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')
sudo curl -fsSLo /usr/local/bin/underpass \
  "https://coord.getunderpass.com/download/latest/underpass-linux-$ARCH"
sudo chmod +x /usr/local/bin/underpass
```

Check it against `https://coord.getunderpass.com/download/latest/SHA256SUMS` if you like.

### 3. Join with the key

```bash
sudo underpass up --auth-key tskey-auth-xxxxxxxxxxxx --hostname db-prod
```

It prints `registered as db-prod (100.64.0.21, …)` and keeps the tunnel up while it runs. `--hostname` is optional; without it, the machine's hostname is used.

The registration is saved in `/var/lib/tailnet`. Stop the command with Ctrl+C once it's registered.

### 4. Keep it running

Create `/etc/systemd/system/underpass.service`:

```ini
[Unit]
Description=Underpass
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/underpass run
Restart=always

[Install]
WantedBy=multi-user.target
```

Then:

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now underpass
```

`underpass run` reconnects using the saved registration, so the key isn't needed again.

### In a provisioning script

```bash
#!/bin/sh
set -e
ARCH=$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')
curl -fsSLo /usr/local/bin/underpass "https://coord.getunderpass.com/download/latest/underpass-linux-$ARCH"
chmod +x /usr/local/bin/underpass
timeout 30 underpass up --auth-key "$UNDERPASS_AUTH_KEY" || true   # registers, then stops after 30s
# … write the systemd unit above, then:
systemctl enable --now underpass
```

Run it as root, with a reusable (and for short-lived machines, ephemeral) key.

## Windows servers

Install with **UnderpassSetup.exe**. When the browser opens for sign-in, finish it from any device, as above.

## If it doesn't connect

| Message | What to do |
|---|---|
| `auth key is invalid, expired, or already used` | Create a new key, or a reusable one |
| `Logged in. An admin needs to approve this machine…` | Approve it under **Machines**, or use a pre-approved key |
| `this workspace's plan allows N machines` | Remove a machine or [upgrade](/manage/plans) |
| `start tunnel (needs root/administrator)` | Run the command with `sudo` |

See [Troubleshooting](/reference/troubleshooting) for more.
