├── system-config.ign ├── human-config.yaml └── README.md /system-config.ign: -------------------------------------------------------------------------------- 1 | { 2 | "ignition": { 3 | "version": "3.3.0" 4 | }, 5 | "storage": { 6 | "files": [ 7 | { 8 | "path": "/var/lib/iptables/rules-save", 9 | "contents": { 10 | "compression": "gzip", 11 | "source": "data:;base64,H4sIAAAAAAAC/5SQQUvEMBCF7/0VcxYHUlxEeotNxMKuKdksHsRDyEaMZjehOx7899JtlSr20NOD974vkLl4CZF8V1TNQ7szILRq4YlV7Lmo7pR+5Fr86tTO9Biva9masUQOg4wBYgJ8G+fJcACXjkfqrHsHREcnsuRByzU3UlzKreG362Z7L8W/doaPfe4fOQfuc+oIVuX1qpzBgzuc+SGxD6TP7IEtFa6WCjdLhfLPJ76vjgE8vTLABGRDPDkbPZtFp0gaxR+0VptNY4qvAAAA//9Y7QzJ6wEAAA==" 12 | }, 13 | "mode": 420 14 | }, 15 | { 16 | "overwrite": true, 17 | "path": "/etc/flatcar/update.conf", 18 | "contents": { 19 | "compression": "gzip", 20 | "source": "data:;base64,H4sIAAAAAAAC/wpydfL3D4kPDglyDHF1j7QtSk3Kzy/h8vF39g729QzxcImHqgj39HPxD48PDnEMCrE1MLIyMMCtyMfVzz3Ew9YwQwEOuAABAAD//xtak2VpAAAA" 21 | }, 22 | "mode": 272 23 | } 24 | ], 25 | "links": [ 26 | { 27 | "overwrite": true, 28 | "path": "/etc/localtime", 29 | "target": "/usr/share/zoneinfo/Etc/UTC" 30 | } 31 | ] 32 | }, 33 | "systemd": { 34 | "units": [ 35 | { 36 | "contents": "[Unit]\nDescription=Tailscale Docker Container\nAfter=docker.service\nRequires=docker.service\n\n[Service]\nExecStart=/usr/bin/docker run \\\n --name tailscale \\\n --rm \\\n --hostname vpn-server \\\n --network host \\\n --label com.centurylinklabs.watchtower.enable=true \\\n -e TS_AUTHKEY=ENTER_TAILSCALE_AUTH_KEY_HERE \\\n -e TS_STATE_DIR=/var/lib/tailscale \\\n -e TS_USERSPACE=false \\\n -e TS_EXTRA_ARGS=\"--advertise-exit-node\" \\\n -e TS_TAILSCALED_EXTRA_ARGS=\"--port=41641 --no-logs-no-support\" \\\n -v tailscale:/var/lib/tailscale \\\n --device=/dev/net/tun:/dev/net/tun \\\n --cap-add=NET_ADMIN \\\n ghcr.io/tailscale/tailscale:latest\nExecStop=/usr/bin/docker stop tailscale\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\n", 37 | "enabled": true, 38 | "name": "tailscale.service" 39 | }, 40 | { 41 | "contents": "[Unit]\nDescription=Watchtower Docker Container Auto-Updater\nAfter=docker.service\nRequires=docker.service\n\n[Service]\nExecStart=/usr/bin/docker run \\\n --name watchtower \\\n --rm \\\n --label com.centurylinklabs.watchtower.enable=true \\\n -e WATCHTOWER_LABEL_ENABLE=true \\\n -e WATCHTOWER_CLEANUP=true \\\n -e WATCHTOWER_NO_RESTART=true \\\n -v /var/run/docker.sock:/var/run/docker.sock \\\n ghcr.io/containrrr/watchtower:latest\nExecStop=/usr/bin/docker stop watchtower\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\n", 42 | "enabled": true, 43 | "name": "watchtower.service" 44 | }, 45 | { 46 | "contents": "[Unit]\nDescription=Restore iptables rules\nAfter=network.target\n\n[Service]\nType=oneshot\nExecStart=/sbin/iptables-restore -n /var/lib/iptables/rules-save\nRemainAfterExit=yes\n\n[Install]\nWantedBy=multi-user.target", 47 | "enabled": true, 48 | "name": "iptables-restore.service" 49 | } 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /human-config.yaml: -------------------------------------------------------------------------------- 1 | variant: flatcar 2 | version: 1.0.0 3 | storage: 4 | files: 5 | - path: /var/lib/iptables/rules-save 6 | mode: 0644 7 | contents: 8 | inline: | 9 | *filter 10 | :INPUT DROP [0:0] 11 | :FORWARD DROP [0:0] 12 | :OUTPUT ACCEPT [0:0] 13 | -A INPUT -i lo -j ACCEPT 14 | -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 15 | -A INPUT -p udp -m udp --dport 41641 -j ACCEPT 16 | -A INPUT -p icmp -m icmp --icmp-type 0 -j ACCEPT 17 | -A INPUT -p icmp -m icmp --icmp-type 3 -j ACCEPT 18 | -A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT 19 | -A INPUT -p icmp -m icmp --icmp-type 11 -j ACCEPT 20 | -A FORWARD -i eth0 -o tailscale0 -j ACCEPT 21 | -A FORWARD -i tailscale0 -o eth0 -j ACCEPT 22 | COMMIT 23 | - path: /etc/flatcar/update.conf 24 | overwrite: true 25 | contents: 26 | inline: | 27 | REBOOT_STRATEGY=reboot 28 | LOCKSMITHD_REBOOT_WINDOW_START=02:00 29 | LOCKSMITHD_REBOOT_WINDOW_LENGTH=1h 30 | mode: 0420 31 | links: 32 | - path: /etc/localtime 33 | overwrite: true 34 | target: /usr/share/zoneinfo/Etc/UTC 35 | systemd: 36 | units: 37 | - name: tailscale.service 38 | enabled: true 39 | contents: | 40 | [Unit] 41 | Description=Tailscale Docker Container 42 | After=docker.service 43 | Requires=docker.service 44 | 45 | [Service] 46 | ExecStart=/usr/bin/docker run \ 47 | --name tailscale \ 48 | --rm \ 49 | --hostname vpn-server \ 50 | --network host \ 51 | --label com.centurylinklabs.watchtower.enable=true \ 52 | -e TS_AUTHKEY=ENTER_TAILSCALE_AUTH_KEY_HERE \ 53 | -e TS_STATE_DIR=/var/lib/tailscale \ 54 | -e TS_USERSPACE=false \ 55 | -e TS_EXTRA_ARGS="--advertise-exit-node" \ 56 | -e TS_TAILSCALED_EXTRA_ARGS="--port=41641 --no-logs-no-support" \ 57 | -v tailscale:/var/lib/tailscale \ 58 | --device=/dev/net/tun:/dev/net/tun \ 59 | --cap-add=NET_ADMIN \ 60 | ghcr.io/tailscale/tailscale:latest 61 | ExecStop=/usr/bin/docker stop tailscale 62 | Restart=always 63 | RestartSec=5 64 | 65 | [Install] 66 | WantedBy=multi-user.target 67 | - name: watchtower.service 68 | enabled: true 69 | contents: | 70 | [Unit] 71 | Description=Watchtower Docker Container Auto-Updater 72 | After=docker.service 73 | Requires=docker.service 74 | 75 | [Service] 76 | ExecStart=/usr/bin/docker run \ 77 | --name watchtower \ 78 | --rm \ 79 | --label com.centurylinklabs.watchtower.enable=true \ 80 | -e WATCHTOWER_LABEL_ENABLE=true \ 81 | -e WATCHTOWER_CLEANUP=true \ 82 | -e WATCHTOWER_NO_RESTART=true \ 83 | -v /var/run/docker.sock:/var/run/docker.sock \ 84 | ghcr.io/containrrr/watchtower:latest 85 | ExecStop=/usr/bin/docker stop watchtower 86 | Restart=always 87 | RestartSec=5 88 | 89 | [Install] 90 | WantedBy=multi-user.target 91 | - name: iptables-restore.service 92 | enabled: true 93 | contents: | 94 | [Unit] 95 | Description=Restore iptables rules 96 | After=network.target 97 | 98 | [Service] 99 | Type=oneshot 100 | ExecStart=/sbin/iptables-restore -n /var/lib/iptables/rules-save 101 | RemainAfterExit=yes 102 | 103 | [Install] 104 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Easy Tailscale VPN Server (Flatcar Container Linux Config) 2 | 3 | **Ignition/Butane configuration file to deploy a firewalled Tailscale exit node as a personal VPN.** 4 | - Designed for use with Flatcar Container OS (may also work with Fedora CoreOS). 5 | - Automatic Tailscale updates (via Watchtower) 6 | - Automatic OS updates (via Flatcar) 7 | - Using Vultr's high frequency tier of VPS, you can achieve ~1Gb/s VPN speeds for $6/m with 1TB of bandwidth. 8 | - Disables most of Tailscale's logging features for security 9 | 10 | **Configuration Tool**: https://jakelmg.github.io/tailscale-flatcar-config-tool/ 11 | 12 | ## Instructions (using the [configuration tool](https://jakelmg.github.io/tailscale-flatcar-config-tool/)) 13 | 1. **Create a free [Tailscale account](https://login.tailscale.com/start)** 14 | 2. **Go to [Settings>Keys](https://login.tailscale.com/admin/settings/keys) and generate an auth key.** 15 | 3. **Open the [configuration tool](https://jakelmg.github.io/tailscale-flatcar-config-tool/), paste in the auth key you just generated, and click "Generate Config" or "Copy" in the preview window.** 16 | - Optionally: 17 | - Enable SSH access and enter an SSH public key 18 | - Select your timezone for automatic update reboot timing / logs 19 | - Adjust the automatic update reboot time window 20 | 4. **Create a VPS on [Vultr](https://lmg.gg/vultr-gh)** (affiliate link) 21 | 1. Select the location you want to deploy your VPN server to. 22 | 2. Select "Shared CPU" as the server "Type" (the other types may also work but are much more expensive) 23 | 3. Disable "Automatic Backups" (they cost money, and we aren't storing data on this VPS anyways) 24 | 4. Select "High Frequency" under Plans, and then "vhf-1c-1gb" 25 | - This is the cheapest high frequency plan, and includes 1 shared CPU core, 1GB of RAM, and 1TB of bandwidth for $6/month. It can handle around 1Gb/s VPN speeds 26 | - Other tiers seemed to have worst performance - but if you have a less than 1Gb/s internet connection, that might not matter, and a cheaper plan might be a better value. 27 | 5. On the "Step 2" page, select the "stable x64" version of Flatcar Container Linux 28 | 6. Paste the config we generated earlier into the "Ignition Configuration" box 29 | 7. Click "Deploy" 30 | 5. **Once `vpn-server` appears in the Tailscale "[Machines](https://login.tailscale.com/admin/machines)" page, approve the device as an exit node.** 31 | 1. Click the "..." icon next to the "vpn-server" machine, then "Edit route settings..." 32 | 2. Check the "Use as exit node" box. 33 | 3. Click "Save" 34 | 6. **[Download Tailscale](https://tailscale.com/download) and install it on the device you want to tunnel through your new VPN.** 35 | 7. **Login into the Tailscale client with the same account you used when generating the auth key.** 36 | 8. **Open cmd/terminal and run `ping vpn-server` to make sure you can ping your vpn server over Tailscale.** 37 | 9. **Run `tailscale status` (mac instructions [here](https://tailscale.com/kb/1080/cli?tab=macos)) and make sure the connection shows "Direct" and not relayed.** 38 | - In most cases, it shouldn't be possible for the connection to relay. If that happens, you may be dealing with an extremely restrictive or non standard firewall on the client. 39 | 10. **Once you've verified you have a working direct connection, select your VPN server as your exit node. This will start tunneling your traffic through your new DIY VPN server :D** 40 | 1. Right click on Tailscale in your device tray > Exit nodes > Select "`vpn-server`" 41 | 11. **Check to see if your external IP address has changed with a service like [ipinfo.io](https://ipinfo.io/)** 42 | - "org" should show "Vultr" or similar 43 | - city/region/count should match the location you selected when creating the VPS on Vultr 44 | - "ip" should be different than your non-VPN'd IP (you can disconnect from Tailscale to compare) 45 | 46 | ## Manual Instructions 47 | 1. **Create a free [Tailscale account](https://login.tailscale.com/start)** 48 | 2. **Go to [Settings>Keys](https://login.tailscale.com/admin/settings/keys) and generate an auth key.** 49 | 3. **Download the human-readable Butane config: human-config.yaml** 50 | 1. Replace `ENTER_TAILSCALE_AUTH_KEY_HERE` with the auth key you generated in step 2. 51 | 2. Optionally: 52 | - Enable SSH by adding a firewall allow rule for port 22/tcp 53 | ``` 54 | - path: /var/lib/iptables/rules-save 55 | mode: 0644 56 | contents: 57 | inline: | 58 | *filter 59 | :INPUT DROP [0:0] 60 | :FORWARD DROP [0:0] 61 | :OUTPUT ACCEPT [0:0] 62 | -A INPUT -i lo -j ACCEPT 63 | -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 64 | -A INPUT -p udp -m udp --dport 41641 -j ACCEPT 65 | -A INPUT -p icmp -m icmp --icmp-type 0 -j ACCEPT 66 | -A INPUT -p icmp -m icmp --icmp-type 3 -j ACCEPT 67 | -A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT 68 | -A INPUT -p icmp -m icmp --icmp-type 11 -j ACCEPT 69 | -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT <--- Add this line to open port 22 for SSH access 70 | -A FORWARD -i eth0 -o tailscale0 -j ACCEPT 71 | -A FORWARD -i tailscale0 -o eth0 -j ACCEPT 72 | COMMIT 73 | ``` 74 | - Add an SSH key in Butane format for the default `core` user. 75 | ``` 76 | passwd: 77 | users: 78 | - name: core 79 | ssh_authorized_keys: 80 | - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGdByTgSVHq......." <--- Replace with your SSH public key 81 | ``` 82 | - Enter your timezone 83 | ``` 84 | links: 85 | - path: /etc/localtime 86 | overwrite: true 87 | target: /usr/share/zoneinfo/Etc/UTC <--- Replace "Etc/UTC" with your desired timezone 88 | ``` 89 | - Adjust the automatic update reboot time window 90 | ``` 91 | - path: /etc/flatcar/update.conf 92 | overwrite: true 93 | contents: 94 | inline: | 95 | REBOOT_STRATEGY=reboot 96 | LOCKSMITHD_REBOOT_WINDOW_START=02:00 <--- Replace "02:00" with your desired start time for the automatic update reboot time window 97 | LOCKSMITHD_REBOOT_WINDOW_LENGTH=1h <--- Replace "1h" with your desired reboot time window length 98 | mode: 0420 99 | ``` 100 | 101 | 4. **Install Butane, which you'll need to convert the human-readable Butane config to a system-readable Ignition config:** 102 | - Windows (in cmd) 103 | ``` 104 | winget install butane 105 | ``` 106 | - MacOS (in terminal, requires [Homebrew](https://brew.sh/)): 107 | ``` 108 | brew install butane 109 | ``` 110 | - Debian/Ubuntu Linux: 111 | ``` 112 | sudo apt install butane 113 | ``` 114 | 5. **Convert the human-readable Butane config to a system-readable Ignition config:** 115 | ``` 116 | butane --pretty --strict human-config.yaml --output=system-config.ign 117 | ``` 118 | 6. **Create a VPS on [Vultr](https://lmg.gg/vultr-gh)** (affiliate link) 119 | 1. Select the location you want to deploy your VPN server to. 120 | 2. Select "Shared CPU" as the server "Type" (the other types may also work but are much more expensive) 121 | 3. Disable "Automatic Backups" (they cost money, and we aren't storing data on this VPS anyways) 122 | 4. Select "High Frequency" under Plans, and then "vhf-1c-1gb" 123 | - This is the cheapest high frequency plan, and includes 1 shared CPU core, 1GB of RAM, and 1TB of bandwidth for $6/month. It can handle around 1Gb/s VPN speeds 124 | - Other tiers seemed to have worst performance - but if you have a less than 1Gb/s internet connection, that might not matter, and a cheaper plan might be a better value. 125 | 5. On the "Step 2" page, select the "stable x64" version of Flatcar Container Linux 126 | 6. Paste the config we generated earlier into the "Ignition Configuration" box 127 | 7. Click "Deploy" 128 | 7. **Once vpn-server appears in the Tailscale "[Machines](https://login.tailscale.com/admin/machines)" page, approve the device as an exit node.** 129 | 1. Click the "..." icon next to the "vpn-server" machine, then "Edit route settings..." 130 | 2. Check the "Use as exit node" box. 131 | 3. Click "Save" 132 | 8. **[Download Tailscale](https://tailscale.com/download) and install it on the device you want to tunnel through your new VPN.** 133 | 9. **Login into the Tailscale client with the same account you used when generating the auth key.** 134 | 10. **Open cmd/terminal and run `ping vpn-server` to make sure you can ping your vpn server over Tailscale.** 135 | 11. **Run `tailscale status` (mac instructions [here](https://tailscale.com/kb/1080/cli?tab=macos)) and make sure the connection shows "Direct" and not relayed.** 136 | - In most cases, it shouldn't be possible for the connection to relay. If that happens, you may be dealing with an extremely restrictive or non standard firewall on the client. 137 | 12. **Once you've verified you have a working direct connection, select your VPN server as your exit node. This will start tunneling your traffic through your new DIY VPN server :D** 138 | 1. Right click on Tailscale in your device tray > Exit nodes > Select "`vpn-server`" 139 | 13. **Check to see if your external IP address has changed with a service like [ipinfo.io](https://ipinfo.io/)** 140 | - "org" should show "Vultr" or similar 141 | - city/region/count should match the location you selected when creating the VPS on Vultr 142 | - "ip" should be different than your non-VPN'd IP (you can disconnect from Tailscale to compare) 143 | 144 |
145 | 146 | ## Deploying Tailscale/Flatcar on Other Cloud Providers 147 | - Digital Ocean (easy & tested) 148 | - Upload Flatcar's official DigitalOcean image as a Custom Image 149 | - [Instructions](https://docs.digitalocean.com/products/custom-images/getting-started/quickstart/) 150 | - [Image](https://stable.release.flatcar-linux.net/amd64-usr/current/flatcar_production_digitalocean_image.bin.bz2) 151 | 152 | - OVH VPS (more difficult & untested) 153 | - Use Flatcar's community supported OpenStack (OVH compatible) image 154 | - [Instructions](https://www.flatcar.org/docs/latest/installing/community-platforms/ovhcloud/) 155 | - [Image](https://stable.release.flatcar-linux.net/amd64-usr/current/flatcar_production_openstack_image.img) --------------------------------------------------------------------------------