5 min read

Connecting a Local Server to Mesh VPN: Tailscale Exit Node Setup on Alpine Linux

Complete guide to installing Tailscale on Alpine Linux, advertising an exit node, and routing your home or office traffic through a remote VPS securely.

Tailscale builds a WireGuard-based mesh VPN on top of your existing network — no port forwarding, no manual key exchange. One of its most powerful features is the exit node: route all internet traffic from your devices through a remote server, useful for secure browsing on public Wi-Fi or accessing geo-restricted resources.

This guide sets up Tailscale on Alpine Linux 3.22 (common for lightweight VPS and homelab nodes) and configures the machine as an exit node. Example VPS hostname: vpn-exit.example.com at IP 203.0.113.20.

Prerequisites

  • A Tailscale account (free tier supports up to 100 devices)
  • A VPS or local server running Alpine Linux 3.19+
  • Root or sudo access
  • IP forwarding enabled on the host

How Exit Nodes Work

┌──────────────┐ WireGuard mesh ┌─────────────────────┐
│ Laptop │ ──────────────────────► │ Alpine VPS │
│ (Tailscale) │ │ (Exit Node) │
│ │ All internet traffic │ 203.0.113.20 │
│ Public WiFi │ exits via VPS IP │ │ │
└──────────────┘ │ ▼ │
│ Internet │
└─────────────────────┘

Your laptop connects to Tailscale. When the exit node is enabled, traffic destined for the public internet is NAT’d through the Alpine server’s public IP.

Step 1: Prepare Alpine Linux

SSH into your Alpine server:

ssh-connect.sh

Update packages and install dependencies:

alpine-base-setup.sh
apk update && apk upgrade
apk add curl iptables ip6tables

Enable IP forwarding (required for exit node traffic):

enable-ip-forwarding.sh
cat >> /etc/sysctl.d/99-tailscale.conf << 'EOF'
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF
sysctl --system

Verify:

Terminal window
sysctl net.ipv4.ip_forward
# Expected: net.ipv4.ip_forward = 1

Step 2: Install Tailscale

Alpine ships Tailscale in the community repository:

install-tailscale.sh
apk add tailscale
rc-update add tailscaled default
rc-service tailscaled start
rc-service tailscaled status

Authenticate and join your tailnet:

tailscale-up.sh
tailscale up

This prints a URL like:

To authenticate, visit:
https://login.tailscale.com/a/xxxxxxxxxxxx

Open the URL in a browser, log in, and authorize the device. Name it something descriptive, e.g. vpn-exit-alpine.

Verify the node is connected:

Terminal window
tailscale status

Example output:

100.64.0.5 vpn-exit-alpine [email protected] linux -

Note the Tailscale IP (100.64.0.5 in this example) — you’ll use it for testing.

Step 3: Configure as Exit Node

Advertise this machine as an exit node:

advertise-exit-node.sh
tailscale up --advertise-exit-node

Approve the exit node in admin console Go to Tailscale Admin Console, find vpn-exit-alpine, click the menu → Edit route settings → enable Use as exit node.

Without admin approval, clients cannot route traffic through this node.

Step 4: Configure NAT (iptables)

Tailscale handles WireGuard tunnels, but the kernel still needs NAT rules so exit traffic is masqueraded on the public interface.

Detect your public network interface:

Terminal window
ip route | grep default
# Example: default via 172.31.1.1 dev eth0

Create an OpenRC init script for persistent NAT rules. Replace eth0 with your actual interface:

setup-nat.sh
cat > /etc/local.d/tailscale-nat.start << 'EOF'
#!/bin/sh
IFACE="eth0"
# Enable forwarding (belt and suspenders)
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1
# NAT for Tailscale exit node traffic
iptables -t nat -C POSTROUTING -o ${IFACE} -j MASQUERADE 2>/dev/null \
|| iptables -t nat -A POSTROUTING -o ${IFACE} -j MASQUERADE
# Allow forwarding from tailscale0
iptables -C FORWARD -i tailscale0 -j ACCEPT 2>/dev/null \
|| iptables -A FORWARD -i tailscale0 -j ACCEPT
iptables -C FORWARD -o tailscale0 -j ACCEPT 2>/dev/null \
|| iptables -A FORWARD -o tailscale0 -j ACCEPT
EOF
chmod +x /etc/local.d/tailscale-nat.start
rc-update add local default
/etc/local.d/tailscale-nat.start

Verify NAT rules:

Terminal window
iptables -t nat -L POSTROUTING -v -n

You should see a MASQUERADE rule on eth0.

Step 5: Make Tailscale Settings Persistent

Alpine’s tailscale up flags reset on reboot unless saved. Create an OpenRC service override:

persistent-tailscale-config.sh
mkdir -p /etc/conf.d
cat > /etc/conf.d/tailscaled << 'EOF'
# Passed to `tailscale up` on boot via custom service
TS_EXTRA_ARGS="--advertise-exit-node --accept-routes"
EOF

Edit the Tailscale OpenRC service to apply flags on start:

patch-tailscale-service.sh
cat > /etc/local.d/tailscale-up.start << 'EOF'
#!/bin/sh
sleep 3
tailscale up --advertise-exit-node --accept-routes --reset
EOF
chmod +x /etc/local.d/tailscale-up.start

ACL-controlled exit nodes In production, restrict who can use exit nodes via Tailscale ACLs. Example policy snippet:

tailscale-acl.json
{
"autoApprovers": {
"routes": {
"0.0.0.0/0": ["autogroup:admin"],
"::/0": ["autogroup:admin"]
}
}
}

Step 6: Connect a Client Device

macOS / Windows / Linux Desktop

  1. Install Tailscale from tailscale.com/download.
  2. Log in with the same account.
  3. Click the Tailscale icon → Exit Node → select vpn-exit-alpine.

CLI alternative on Linux/macOS:

use-exit-node-client.sh
# List available exit nodes
tailscale exit-node list
# Route all traffic through the exit node
tailscale set --exit-node=vpn-exit-alpine
# Verify your public IP changed to the VPS IP
curl -4 ifconfig.me
# Expected: 203.0.113.20

To stop using the exit node:

Terminal window
tailscale set --exit-node=

Mobile (iOS / Android)

Open the Tailscale app → Exit Node → select vpn-exit-alpine → enable Allow LAN access if you still need local network devices.

Step 7: Connect a Local Server (Subnet Router)

If you want your entire home LAN (192.168.1.0/24) reachable over Tailscale without installing the client on every device, advertise the subnet:

On your Alpine box at home (or any Linux gateway):

subnet-router.sh
# Advertise your local network
tailscale up --advertise-routes=192.168.1.0/24 --accept-routes

Approve the subnet route in the admin console under Subnet routes.

Other Tailscale devices can now reach 192.168.1.x addresses directly:

Terminal window
# From a remote laptop on Tailscale
ping 192.168.1.50

Example topology:

┌─────────────────────────────────────────────────────────┐
│ Home LAN: 192.168.1.0/24 │
│ │
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ NAS │ │ Alpine Router │──► Tailscale │
│ │ 192.168.1.50│◄──►│ 192.168.1.1 │ mesh │
│ └─────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────┘
Remote laptop (100.64.0.10)
can ping 192.168.1.50 directly

Step 8: Firewall Hardening

Alpine doesn’t ship ufw, but you can use iptables directly. Allow SSH and Tailscale; block everything else inbound:

alpine-firewall.sh
apk add iptables
cat > /etc/local.d/firewall.start << 'EOF'
#!/bin/sh
# Allow established connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
# Allow SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Allow Tailscale (WireGuard UDP)
iptables -A INPUT -i tailscale0 -j ACCEPT
iptables -A INPUT -p udp --dport 41641 -j ACCEPT
# Drop everything else inbound
iptables -A INPUT -j DROP
EOF
chmod +x /etc/local.d/firewall.start
/etc/local.d/firewall.start

Tailscale and port 41641 Tailscale uses UDP port 41641 for direct peer connections. In most cases it works through NAT without manual port forwarding, but allowing it inbound helps with direct paths.

Step 9: DNS Configuration (Optional)

Use Tailscale MagicDNS so devices resolve each other by hostname:

  1. Admin console → DNS → enable MagicDNS.
  2. Add a custom nameserver if needed (e.g. AdGuard at 100.64.0.5).
  3. On Alpine, verify:
Terminal window
tailscale status --json | grep DNS

Access your exit node by name instead of IP:

Terminal window
ping vpn-exit-alpine

Step 10: Monitoring & Verification

Run these checks after setup:

verification.sh
# Tailscale connectivity
tailscale status
tailscale netcheck
# Confirm exit node is advertised
tailscale debug prefs | grep ExitNode
# Check NAT is active
iptables -t nat -L POSTROUTING -v -n | grep MASQUERADE
# Test from a client (should return VPS public IP)
curl -4 ifconfig.me

Enable Tailscale’s built-in logging for troubleshooting:

Terminal window
tailscale set --shields-up=false
logread -f | grep tailscale

Complete Setup Script

For a fresh Alpine 3.22 VPS, save this as setup-exit-node.sh:

setup-exit-node.sh
#!/bin/sh
set -e
IFACE="${1:-eth0}"
echo "[1/6] Installing packages..."
apk update && apk upgrade
apk add curl tailscale iptables ip6tables
echo "[2/6] Enabling IP forwarding..."
cat > /etc/sysctl.d/99-tailscale.conf << EOF
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF
sysctl --system
echo "[3/6] Starting tailscaled..."
rc-update add tailscaled default
rc-service tailscaled start
echo "[4/6] Configuring NAT on ${IFACE}..."
cat > /etc/local.d/tailscale-nat.start << SCRIPT
#!/bin/sh
IFACE="${IFACE}"
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -C POSTROUTING -o \${IFACE} -j MASQUERADE 2>/dev/null \\
|| iptables -t nat -A POSTROUTING -o \${IFACE} -j MASQUERADE
iptables -C FORWARD -i tailscale0 -j ACCEPT 2>/dev/null \\
|| iptables -A FORWARD -i tailscale0 -j ACCEPT
iptables -C FORWARD -o tailscale0 -j ACCEPT 2>/dev/null \\
|| iptables -A FORWARD -o tailscale0 -j ACCEPT
SCRIPT
chmod +x /etc/local.d/tailscale-nat.start
rc-update add local default
/etc/local.d/tailscale-nat.start
echo "[5/6] Joining tailnet (interactive)..."
tailscale up --advertise-exit-node --accept-routes
echo "[6/6] Done! Approve exit node at:"
echo " https://login.tailscale.com/admin/machines"
echo ""
echo "Test from a client:"
echo " tailscale set --exit-node=$(hostname)"
echo " curl -4 ifconfig.me"

Run it:

Terminal window
chmod +x setup-exit-node.sh
./setup-exit-node.sh eth0

Troubleshooting

Exit node not appearing in client list

  • Confirm --advertise-exit-node is active: tailscale debug prefs | grep Advertise
  • Approve the route in the admin console
  • Restart: rc-service tailscaled restart && tailscale up --advertise-exit-node

Traffic not routing through VPS

Terminal window
# On the client
tailscale status | grep exit
# On the Alpine server — check NAT counters are incrementing
iptables -t nat -L POSTROUTING -v -n

If NAT counters stay at zero, verify net.ipv4.ip_forward=1 and the correct IFACE in the NAT script.

Alpine reboot loses iptables rules

Ensure both scripts are in /etc/local.d/ and rc-update add local default is enabled:

Terminal window
rc-update show | grep local
ls -la /etc/local.d/

DNS leaks when using exit node

Force all DNS through Tailscale:

Terminal window
# On client
tailscale set --accept-dns=true

Or configure global DNS in admin console.

Security Best Practices

  • Enable Tailscale ACLs to restrict exit node usage
  • Use SSH keys only on the Alpine VPS
  • Keep Alpine updated: apk update && apk upgrade
  • Enable key expiry for device re-authentication
  • Monitor auth logs: tailscale status --json
  • Do not expose unnecessary ports — Tailscale needs no inbound port forwarding

Conclusion

You now have:

  • An Alpine Linux node in your Tailscale mesh at vpn-exit-alpine (100.64.0.5)
  • A working exit node routing internet traffic through 203.0.113.20
  • Optional subnet routing to reach your home LAN remotely
  • Persistent NAT and firewall rules surviving reboots

Combine with Vaultwarden Run both services on the same 1GB VPS: Vaultwarden on vault.example.com for passwords, Tailscale exit node for secure browsing. They use different ports and coexist comfortably under ~200 MB RAM combined.

Found this article helpful? Share it