Tailscale Exit Node on FreeBSD
We needed a simple and secure way to run Tailscale as a VPN exit node on a FreeBSD server so we documented it here for future reference. Hope it helps someone else as well.
Why do we need a VPN exit node? For us we whitelist IP addresses that are allowed to access our datacenter servers' iDRAC interface. By having a specific exit node that the team can all route through, we can ensure that only whitelisted IP addresses can access the iDRAC interface.
Here are the simple steps to set up a Tailscale exit node on FreeBSD:
1. Install Tailscale.
pkg update
pkg install tailscale
2. Create the Tailscale state directory.
install -d -o root -g wheel -m 0750 /var/db/tailscale
3. Enable forwarding and start 'tailscaled'.
For exit-node mode on FreeBSD, 'tailscaled' should run as 'root' (it must manage routing, NAT, and interfaces).
If it asks to authenticate when you run tailscaled service for the first time, follow the link that appears in the terminal.
# Persistent (survives reboot):
sysrc tailscaled_enable="YES"
sysrc tailscaled_state_dir="/var/db/tailscale"
sysrc tailscaled_exitnode_enable="YES"
sysrc tailscaled_up_args="--ssh"
sysrc gateway_enable="YES"
# Apply immediately now (no reboot required):
sysctl net.inet.ip.forwarding=1
service tailscaled start
tailscale status
'--ssh' enables Tailscale SSH, so SSH access is tied to your tailnet identity and policy instead of manually distributing SSH keys to 'authorized_keys'.
If startup prints a login URL, open it once to authorize the node. If you use custom tailnet policy, make sure you keep both network access rules and SSH rules for this node.
4. Configure a minimal 'ipfw' firewall for exit-node traffic.
Replace 'vtnet0' with your WAN interface name. You can find the interface name by running 'ifconfig' and looking for the interface with an IP address in the 'inet' column.
Create '/etc/ipfw.rules':
#!/bin/sh
ipfw -q -f flush
# NAT for Tailscale client traffic going to the internet.
ipfw -q nat 1 config if vtnet0 reset same_ports
# Safe baseline.
ipfw -q add 10 allow ip from any to any via lo0
ipfw -q add 20 allow ip from any to any via tailscale0
ipfw -q add 30 check-state
# Optional but recommended for better peer connectivity.
ipfw -q add 40 allow udp from any to me 41641 in recv vtnet0
# Exit-node forwarding + NAT.
ipfw -q add 50 nat 1 ip from 100.64.0.0/10 to any out via vtnet0
ipfw -q add 60 allow ip from 100.64.0.0/10 to any out via vtnet0 keep-state
# Host outbound traffic.
ipfw -q add 70 allow ip from me to any out via vtnet0 keep-state
# Default deny.
ipfw -q add 1000 deny log ip from any to any
Enable and load rules:
chmod 700 /etc/ipfw.rules
sysrc firewall_enable="YES"
sysrc firewall_nat_enable="YES"
sysrc firewall_script="/etc/ipfw.rules"
service ipfw restart
5. Turn off all WAN-exposed ports, including OpenSSH (optional hardening).
First, verify you can log in with Tailscale SSH from another tailnet device:
tailscale ssh root@<node-name>
ssh root@<node-name> # Can use the built in SSH client if you have a Tailscale client installed on your local MacOS machine.
Then disable the OpenSSH daemon:
# Persistent (survives reboot):
sysrc sshd_enable="NO"
# Apply immediately now:
service sshd stop
To close all inbound WAN ports, remove the optional Tailscale UDP '41641' allow rule and reload 'ipfw':
sed -i '' '/41641 in recv/d' /etc/ipfw.rules
service ipfw restart
In this mode, the node still works in Tailscale, but direct inbound internet ports are closed by default deny rules.
NOTE: you may need to restart your server after making these changes.
Verification
service tailscaled status
ipfw list
tailscale status
sockstat -4 -l | egrep '(:22|:41641)' || true
6. Approve the server as an exit node.
If the server is not already approved as an exit node, you'll need to go to the Tailscale admin console and approve it as an exit node.
7. Conclusion
You're now ready to enjoy Tailscale on your FreeBSD server as an exit node! Cheers 🥂