Automated Apple Time Machine backups to a network share accessed via Tailscale.
macOS Time Machine relies on mDNS/Bonjour to discover network backup destinations and auto-start backups. Bonjour operates via multicast, which doesn't traverse Tailscale's point-to-point WireGuard tunnels. This means Time Machine will never automatically back up to a Tailscale-reachable SMB share — even though the share is fully accessible.
A two-part setup:
- Server — A Proxmox LXC container running a Dockerized Samba server configured as a Time Machine target
- Client — A macOS LaunchAgent that periodically checks connectivity, mounts the share, triggers
tmutil startbackup, and unmounts afterward
┌──────────────┐ WireGuard ┌──────────────────────────────┐
│ │ (Tailscale) │ Proxmox VE Host │
│ macOS │◄──────────────────────────► │ │
│ │ │ ┌────────────────────────┐ │
│ LaunchAgent │ │ │ LXC Container │ │
│ runs every │ SMB over Tailscale │ │ │ │
│ 2 hours │ ◄─────────────────────────► │ │ Docker: Samba + TM │ │
│ │ │ │ │ │
│ tmutil │ │ └──────────┬─────────────┘ │
│ startbackup │ │ │ bind mount │
│ │ │ ┌──────────▼─────────────┐ │
└──────────────┘ │ │ /mnt/usb-backup │ │
│ │ (USB disk / storage) │ │
│ └────────────────────────┘ │
└──────────────────────────────┘
- Proxmox VE host with an LXC container (Debian/Ubuntu) capable of running Docker
- Tailscale installed on both the LXC container and the Mac
- A disk (USB, SSD, NAS mount, etc.) available on the Proxmox host for backup storage
- macOS with Time Machine and Tailscale installed
Create an unprivileged Debian/Ubuntu LXC container with Docker support:
# Key settings for the container (in /etc/pve/lxc/YOUR_CT_ID.conf):
# - features: nesting=1,keyctl=1 (required for Docker)
# - Adequate RAM (2 GB recommended)If you plan to run Tailscale inside the LXC, also add:
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file
On the Proxmox host, mount the disk and create a bind mount for the container:
# Find your disk
lsblk
# Create mount point and add to fstab (adjust UUID and path)
mkdir -p /mnt/your-disk
echo "UUID=YOUR_DISK_UUID /mnt/your-disk ext4 defaults,nofail 0 2" >> /etc/fstab
# Create the directory for Time Machine data
mkdir -p /mnt/your-disk/timemachine
# Bind mount into a path the LXC can access
mkdir -p /mnt/timemachine
echo "/mnt/your-disk/timemachine /mnt/timemachine none bind,nofail 0 0" >> /etc/fstab
mount -aAdd the mount point to the LXC config (/etc/pve/lxc/YOUR_CT_ID.conf):
mp0: /mnt/timemachine,mp=/mnt/timemachine
# Inside the LXC container
apt update && apt install -y curl
curl -fsSL https://get.docker.com | sh# Copy the Samba config
mkdir -p /opt/timemachine-config
cp pve/smb.conf /opt/timemachine-config/smb.confEdit pve/docker-compose.yml — set your password and adjust volume paths if needed, then:
# Copy compose file and start
cp pve/docker-compose.yml /opt/timemachine-config/
cd /opt/timemachine-config
docker compose up -dcurl -fsSL https://tailscale.com/install.sh | sh
tailscale upNote the Tailscale hostname (e.g., your-lxc.your-tailnet.ts.net) — you'll need it for the Mac setup.
# Check Samba is running
docker logs timemachine
# Test SMB access from another machine
smbclient -L //localhost -U backupConnect to the share once manually to save credentials:
- In Finder, press Cmd+K
- Enter:
smb://backup@your-tailscale-hostname/TimeMachine - Enter the password and check "Remember this password in my keychain"
sudo tmutil setdestination "smb://backup@your-tailscale-hostname/TimeMachine"Verify:
tmutil destinationinfo# Create a directory for the script (or use any path you prefer)
mkdir -p ~/Scripts
# Copy and edit the script — update the configuration variables at the top
cp mac/timemachine-backup.sh ~/Scripts/
chmod +x ~/Scripts/timemachine-backup.shEdit ~/Scripts/timemachine-backup.sh and set:
| Variable | Description | Example |
|---|---|---|
SMB_URL |
Full SMB URL for the share | smb://backup@my-tm.tail1234.ts.net/TimeMachine |
TM_HOST |
Tailscale hostname to ping | my-tm.tail1234.ts.net |
BACKUP_INTERVAL |
Seconds between backups | 86400 (24h) |
VOLUME_NAME |
Mount name under /Volumes/ | TimeMachine |
# Copy and edit the plist — update paths to match your username and script location
cp mac/com.timemachine-auto.plist ~/Library/LaunchAgents/
# Edit the plist: replace YOUR_USERNAME with your macOS username
sed -i '' "s/YOUR_USERNAME/$(whoami)/g" ~/Library/LaunchAgents/com.timemachine-auto.plist
# Load the agent
launchctl load ~/Library/LaunchAgents/com.timemachine-auto.plist| Setting | File | Default | Description |
|---|---|---|---|
BACKUP_INTERVAL |
timemachine-backup.sh |
86400 (24h) |
Minimum seconds between backups |
SMB_URL |
timemachine-backup.sh |
— | SMB URL matching your tmutil destination |
TM_HOST |
timemachine-backup.sh |
— | Tailscale hostname for connectivity check |
VOLUME_NAME |
timemachine-backup.sh |
TimeMachine |
Share name as mounted under /Volumes/ |
StartInterval |
com.timemachine-auto.plist |
7200 (2h) |
How often the LaunchAgent triggers the script |
PASSWORD |
docker-compose.yml |
— | Samba user password |
TM_USERNAME |
docker-compose.yml |
backup |
Samba username |
fruit:time machine max size |
smb.conf |
1500G |
Maximum space for Time Machine backups |
# Is a backup running?
tmutil status
# Last backup info
tmutil destinationinfo
# View the automation log
cat ~/Library/Logs/timemachine-auto.log | tail -20# Run the script manually
bash ~/Scripts/timemachine-backup.sh
# Or trigger Time Machine directly (share must be mounted)
tmutil startbackup --block# Stop the agent
launchctl unload ~/Library/LaunchAgents/com.timemachine-auto.plist
# Start the agent
launchctl load ~/Library/LaunchAgents/com.timemachine-auto.plist
# Check if it's loaded
launchctl list | grep timemachine# Force the next check to trigger a backup
rm ~/.timemachine-last-backup# Samba logs inside the Docker container
docker logs timemachine
# Follow logs in real time
docker logs -f timemachine- Check Tailscale is connected on both sides:
tailscale status - Verify the host is reachable:
ping your-tailscale-hostname - Check the script log:
cat ~/Library/Logs/timemachine-auto.log - Verify the LaunchAgent is loaded:
launchctl list | grep timemachine - Ensure credentials are in keychain: Keychain Access > search for your Tailscale hostname
- Verify the SMB URL is correct:
smb://backup@your-tailscale-hostname/TimeMachine - Test manually: Finder > Cmd+K > paste the SMB URL
- Check Samba is running on the server:
docker logs timemachine
This is normal — it means the LaunchAgent fired while a backup was still running. The script skips to avoid conflicts.
Some exit codes from tmutil are non-fatal (e.g., minor verification warnings). Check the log for details. If backups are completing and data is being written, these can usually be ignored.
- Ensure the LXC has
nesting=1,keyctl=1in features - Verify the bind mount is accessible:
ls -la /mnt/timemachineinside the LXC - Check Docker permissions:
docker ps
- Adjust
fruit:time machine max sizeinsmb.confand restart the container - Time Machine automatically prunes old backups, but only up to the configured limit