Knowledge baseNetworks
Move your headscale network to mesh-net
Step by step, no command-line experience needed: pack your own headscale network with one line, download the file, upload it and move the devices — they keep their addresses.
On this page
You run your own headscale server and want your network in mesh-net — with the same devices, addresses and names. This guide takes you there step by step, and you don't need to know any commands: every command is ready to copy, and you type your server's address only once. A small script on your server does the technical part. The way back — from mesh-net to a server of your own — is in Run your network on your own server.
- Connect to your server from your computer.
- Pack the network into one file — one line, the script does the rest.
- Download the file to your computer.
- Upload it to mesh-net.
- Move the devices — two short commands on each.
- Turn the old server off.
Before you start
- Your headscale server and a way in. A server is the computer where your headscale works —
usually a rented virtual server (VPS). You log in to it over SSH — a secure way to type commands on the server
from your own computer. You need the server's IP address, a user name (often
root) and its password — they are in your hosting provider's email or control panel. - headscale from 0.23 to 0.28 with its data in SQLite — the usual setup. Not sure? The script checks this itself and tells you what to do.
- In mesh-net: a paid private network and a free reserved name. The import creates a private
network, and a private network is a paid service: if you haven't paid for one yet, the import form offers to pay
first, and you choose the file after the payment. The name becomes the address of the new network, like
office.mesh-net.space. See A private network and Reserve a name without records. - About half an hour. Your devices keep working the whole time: you switch them one after another, each with two short commands, and until a device switches, it stays in the old network.
What comes along: the devices with their addresses, names and keys, the users, the routes of subnet routers, the
access policy if headscale kept it in its database — and your server's own key with the settings of the device names,
so full names like nas.tailnet.example.com stay the same, and the DNS settings your server gives the
devices: name servers, split DNS, search domains and extra records. What doesn't: the rest of headscale's settings,
access rules kept in a file and extra records kept in a file (dns.extra_records_path) — the script and the
import tell you if that's your case. The details are in
What the import accepts.
Do your devices reach your server by a name in a domain of your own, like hs.example.com? Then they
can move without a single command on them — see
Move without touching the devices.
Connect to your server
A terminal is a window where you type commands. Open it on your own computer:
- Windows 10 and 11: press Start, type
PowerShelland open Windows PowerShell (on Windows 11 the “Terminal” app works too). Thesshandscpcommands this guide uses are already there. - macOS: press Cmd+Space, type
Terminaland press Enter. - Linux: open Terminal from the applications menu (on Ubuntu — Ctrl+Alt+T).
Type your server's IP address here once — it goes into every command on this page:
Now copy this line into the terminal and press Enter:
ssh root@SERVER-IP
If your provider gave you a user other than root — on Ubuntu servers it is often ubuntu —
use that name instead:
ssh ubuntu@SERVER-IP
- The first time, ssh asks
Are you sure you want to continue connecting (yes/no/[fingerprint])?— typeyesand press Enter. It is your server introducing itself; ssh will remember it. - Then it asks for the password. While you type it, nothing appears on the screen — that's normal. Press Enter.
- You're in when the line in the window starts with something like
root@my-server:~#: the commands you type now run on the server. To leave the server later, typeexit. Permission denied— the password or the user name is wrong; try again.Connection timed out— the address is wrong or the server is off.ssh is not recognizedon Windows — add “OpenSSH Client” in Settings → Apps → Optional features.
Pack the network
In the same window, connected to the server, paste this line and press Enter:
curl -fsSL https://mesh-net.space/ext/mesh-net-pack.sh | sudo bash -s -- --lang en --max-mb 64
It downloads the script from mesh-net and runs it as the server's administrator. The script is plain text — you can
read it at https://mesh-net.space/ext/mesh-net-pack.sh; to check a downloaded copy, sha256sum shows
13c1927d211fb0f7c95e0c82f68a1b958e9a2fe738c5976736637ec8edc1d49b.
If you see
sudo: command not found, you are the administrator already — use this line instead:
curl -fsSL https://mesh-net.space/ext/mesh-net-pack.sh | bash -s -- --lang en --max-mb 64
And if it says curl: command not found, this one does the same with wget:
wget -qO- https://mesh-net.space/ext/mesh-net-pack.sh | sudo bash -s -- --lang en --max-mb 64
The script finds your headscale, shows what it found and asks:
Pack it into /root/mesh-net-import-….tar.gz? [Y/n]— press Enter.- Only if the server has neither sqlite3 nor python3:
Stop headscale for a few seconds, copy the database and start it again? [Y/n]— press Enter. The devices keep working; for a couple of seconds they can't reach the server, which they don't notice. - Only if it finds several headscale servers or none: which one — type its number, or the path to its
config.yaml.
When everything is fine, the end of its output looks like this:
Done. Your network is packed:
/root/mesh-net-import-2026-10-06.tar.gz (4 KB; devices: 3, users: 1)
Keep this file like a password: it holds the keys of your devices and of your server.
What next
1. Download the file to your computer. …
scp root@SERVER-IP:/root/mesh-net-import-2026-10-06.tar.gz .
2. In mesh-net open My networks → Import and choose this file.
The script copies headscale's database without stopping it and checks the copy, and puts the config and the
server's own key next to it; headscale itself is not changed. On the server it only creates the file and the log
/var/log/mesh-net-pack.log, and it lists what it changed at the end. If it stops with a message instead,
see If something goes wrong.
The server's key is what your devices know your server by, and the config holds the endings of the device names:
with both, the network in mesh-net keeps your server's identity and the full names of the devices. If the script
can't take the key, it says so in one line, Without the server's key a seamless move is impossible — the
devices are moved with a command, with the reason in brackets. The file still works: the devices move with the
commands of Move the devices either way.
It holds the keys of your devices and of your server: whoever has it can pretend to be them. Don't send it to anyone; mesh-net only needs it for the upload.
Download the file to your computer
Open a new terminal window — on your computer, not on the server (or type exit in the
current one) — and run the command the script printed at the end, or this one, the same with your address:
scp 'root@SERVER-IP:mesh-net-import-*.tar.gz' .
It asks for the same password as ssh. If you logged in as another user, put that name instead of
root: the script left the file in that user's home folder, and this command looks for it there.
scp 'ubuntu@SERVER-IP:mesh-net-import-*.tar.gz' .
If there are several such files, take the newest — the date is in the name. The file lands in the folder the
terminal is in — usually your home folder: C:\Users\<you> on
Windows, your user folder on macOS and Linux. To open that folder: explorer . on Windows,
open . on macOS.
Prefer a program with windows? On Windows, WinSCP; on macOS,
Cyberduck. Connect the same way as with ssh — the server's address,
the user, the password, SFTP — and drag the file mesh-net-import-….tar.gz to your computer.
Upload it to mesh-net
- In mesh-net open My networks and press Import at the top right: the form Import a network from your headscale or from an Export archive opens. No button? It needs a free reserved name — see Before you start. The form shows the price and the payment buttons instead of the file field? Pick the address and pay for the network: you come back to this form with the address selected, and go on.
- In Address of the new network pick the name that becomes the network's address.
- In Archive (.tar.gz) made by Export, or db.sqlite of your headscale press Choose file
and pick the downloaded
mesh-net-import-….tar.gz. There's no separate field for the config: the script has put it into the file, together with the server's key. - Press Import in the form. mesh-net checks the file first; if it refuses, nothing is created — see mesh-net refused the file.
The new network takes the server's key, the device-name settings and the DNS settings from the file, so the move is
gentler: the full names of the devices stay the same, and so do the name servers, split DNS, search domains and extra
records they get. A bare db.sqlite of your headscale is accepted too, but it brings only the devices: their
full names change to the new network's, the DNS settings are mesh-net's, and the server's key is new.
If your config keeps extra records in a file (dns.extra_records_path), that file stays on your server: the
message says so, and those names don't resolve in mesh-net. Move the records into dns.extra_records of
config.yaml and pack again.
In a few seconds the page of the new network opens with The network office.mesh-net.space is imported: 5 devices, 2 users. Now move the devices — the command is below. Full device names stay as on your server: they end with tailnet.example.com. and the card Move your devices here: the commands and the list of devices — Online here now: 0 of 5 devices. At this point the devices still work through your old server.
Move the devices
The address of the new network is at the top of its page in mesh-net, like office.mesh-net.space. Type
it here once — it goes into the commands below:
Each device switches with two short commands — leave your old server, come to the new one — and keeps its address,
name and keys: no new login. Copy both lines at once with Copy and run them on each device, one device after another,
in a terminal of that device (on a server — after ssh to it, as above).
Linux and macOS
sudo tailscale down
sudo tailscale up --login-server=https://NETWORK-ADDRESS
sudo may ask for your password. All is well when no error appears; tailscale status then lists the
other devices. On a server where you are root and the answer is sudo: command not found, the
same without sudo:
tailscale down
tailscale up --login-server=https://NETWORK-ADDRESS
On macOS this works for Tailscale installed with Homebrew; for the app from the App Store or the website, see Phones and apps.
Windows
Open PowerShell as administrator: Start → type PowerShell → right-click → “Run as administrator”.
Then:
tailscale down
tailscale up --login-server=https://NETWORK-ADDRESS
If tailscale asks to mention all settings
If a device was set up with extra settings — it shared a subnet, was an internet exit, used other devices' subnets
or got its own name — tailscale up answers Error: changing settings via 'tailscale up' requires
mentioning all non-default flags and prints a full command below it, something like:
tailscale up --login-server=https://NETWORK-ADDRESS --advertise-routes=192.168.1.0/24 --accept-routes
That line already has the new address and every setting of this device. Copy the line tailscale printed (not this
example) and run it — with sudo in front on Linux and macOS. Until you do, the device stays
disconnected.
Subnet routers and exit nodes
A device that gives access to a home or office network (a subnet router) or lets others go online through it (an exit node) moves the same way; tailscale will most likely ask you to mention its settings — see above. Move it together with the devices that use it: until it moves, the devices already in mesh-net can't reach its subnet. The routes you approved on your server stay approved.
Phones and apps
We checked the move on Linux devices. On Windows and macOS the commands above are the same commands of the same
program, but we didn't try them there; and switching the server inside the Tailscale apps — iPhone, iPad, Android, and
the apps with windows on macOS and Windows — wasn't tested at all. If a device doesn't come along, the sure way is to
add it again: on the network page press
Add device, then follow Add a device — in the app choose
“Use custom coordination server” (iPhone, iPad) or “Use an alternate server” (Android) and enter
https:// and the address of the new network. The device joins with a new address; its old entry stays
offline in Devices — delete it.
The card Move your devices here counts the devices that already moved. Device names keep working: the
short ones (like nas) and the full ones — they end the same way as on your server, like
nas.tailnet.example.com, because the file brought your server's settings of the names. Only after an
upload of a bare db.sqlite do full names end with the new network's name — see
Device names.
Turn the old server off
When the card shows every device online, turn your old headscale off: devices left on it would keep a second copy of the network. Connect to it (as before) and run — if headscale was installed as a package:
sudo systemctl disable --now headscale
If it runs in docker (the script's line Found: said “docker container” and its name), with that name
in place of headscale — so that it doesn't start again after a reboot:
sudo docker update --restart=no headscale && sudo docker stop headscale
- Don't delete the old server's data for a week or two: if something goes wrong, you can go back.
- If your headscale kept access rules in a file, set them in mesh-net now — on the network's Access tab, see Access rules. Until then every device of the network reaches every other.
- Hide on the card removes it. Delete the archive from your computer, or keep it in a safe place as a backup.
Move without touching the devices (if your server has its own name)
If your devices reach your headscale by a name in a domain of your own — like hs.example.com — the move
can be invisible to them. mesh-net takes over that very name, with your server's key: the devices keep their address,
key, names and the server they are set up with, and switch to mesh-net by themselves. You run nothing on them. The DNS of
your domain stays where it is: at your DNS provider the name becomes a CNAME record of the network's address in
mesh-net, and the domain's other records and services aren't touched. Type the names once:
- A day before, lower the TTL of the record of
SERVER-NAMEat your DNS provider to a minute or a few. DNS caches keep a record as long as its TTL says, and steps 4 and 5 wait for them: with a TTL of an hour, each of them takes an hour. - Pack the network and import it to the temporary address
NETWORK-ADDRESS— the steps Pack, Download and Upload above. Don't move the devices. If you like, check with one device that can afford it:
It moves without a new login. Until the switch it sees only the devices that are in mesh-net, and later it keeps working through this address.sudo tailscale down sudo tailscale up --login-server=https://NETWORK-ADDRESS - Right before the switch, upload a fresh copy. Run the packing script on your server again, download the new file, and on the network's page open Backup → Restore from a backup and upload it. Otherwise everything that changed on your server since the import — new devices, names, routes — is lost.
- Change the network's address to
SERVER-NAME: on the Settings tab type it into New address and press Continue. The next page shows the two records to set at your DNS provider:Add the TXT record. Then replace the record of the name — the A record of your old server — with the CNAME: delete it and add the CNAME at once. The devices notice nothing: they stay with the old server. Press Check: public DNS resolvers show the CNAME once the old record's TTL runs out in their caches. When What DNS shows now says both are visible, tick the box and press Change the address right away: until then a device that looks up the name anew comes to mesh-net, which doesn't serve it yet. mesh-net gets a certificate for the name (usually in seconds) and restarts the server with the new address. If it can't get the certificate within a minute and a half, nothing changes in the network, and the page says why right above its buttons (see If the seamless move goes wrong). The temporary address stays the network's second address: mesh-net keeps it pointing at the network's server, so the CNAME never has to change.Type Name Value TXT _mesh-net-verify.SERVER-NAMEthe value on the page: it proves that the name is yours CNAME SERVER-NAMENETWORK-ADDRESS - Wait out the DNS caches, then turn the old server off. While your old server works, the devices stay with it. The card The address is now hs.example.com on the network's page says until when DNS caches may still give out the old address (the TTL of the old record). After that, turn the old server off (as above): within seconds the devices are online in mesh-net — Online here now: 5 of 5 devices.
The TXT record can be deleted after the change. Once a day mesh-net checks that the name is still a CNAME of the network's address, and if it stops — the record was changed or deleted at the provider — writes to you.
Or move the DNS of the whole domain to mesh-net
If you'd rather manage the domain's records in mesh-net anyway, first move its DNS here — see
Your own domain — and go on when the domain is active. The records come over
as they are: SERVER-NAME still leads to your old server, and the devices notice
nothing. Then the steps are the same, and step 4 is shorter: there are no records to set at a provider — mesh-net points
the name at the network's server itself. Lower the TTL of the record on the domain's page (step 1).
On a test bench, both ways. With the DNS staying at another provider: headscale 0.28 with its own name, certificate, device names and DNS settings, four Linux devices (one of them a subnet router), DNS resolvers that keep answers for their TTL, and a separate DNS server of the “provider” with the domain's site, mail and other records. After the two records were set, the page saw them in 79 seconds (the resolvers still kept the old record); the change took 2.3 seconds (the certificate — 1 second, the restart — 0.6 seconds). After the old server was turned off, all devices were online in mesh-net within a second without a single command; traffic between the devices and through the subnet router didn't stop at all; their addresses, full names, the route and the DNS settings stayed the same, and the domain's other records weren't touched. The device moved to the temporary address kept working through it. With the domain's DNS moved to mesh-net — the same, within 1.4 seconds. Not checked: the real Let's Encrypt and public resolvers (the bench has its own), phones and the Tailscale apps for Windows and macOS (they keep the same key and address, so they should behave the same way), headscale older than 0.28, and resolvers that keep an answer longer than its TTL.
If the seamless move goes wrong
- The page doesn't see the records: check their names — some providers want only the part before the domain, like
hsforhs.example.com, and add the domain themselves — and that the A record of the name is gone: a name with a CNAME can't have other records. A replaced record shows up only when the old one's TTL runs out in the resolvers' caches; press Check again later. If your provider can proxy a name through its own servers (Cloudflare's orange cloud, for example), turn that off for this name: the world then sees the provider's addresses instead of the CNAME, and the page says the name leads to them. - The change fails because of the certificate. The page of the change says so in a box above the buttons, and nothing changes in the network: your old server still serves the devices that are with it — keep it running. The name already leads to mesh-net (the check has just seen its CNAME, or the domain's DNS is in mesh-net), so it's a failure on mesh-net's side: mesh-net couldn't get a certificate for hs.example.com, and this is a failure on mesh-net's side: we already know about it and are working on it. Nothing changed in your network: it works at office.mesh-net.space as before. Don't touch the records at your DNS provider — they're right. Try the change again later. mesh-net's administrators already have the cause. If the cause is on your side, the page names it instead — the domain's CAA records that don't allow Let's Encrypt, or name servers of your domain that aren't mesh-net's any more (see If the change fails).
- A device stays offline after the old server is off: its DNS cache may still hold the old address — wait until
the time on the card has passed. If the network was imported from a bare
db.sqlite, it has its own key instead of your server's, and devices don't come over by themselves: run the commands of Move the devices on them, or restart Tailscale on each. - Changed your mind: start the old server again, give the network another address on its Settings tab
(the temporary one, for instance), remove
SERVER-NAMEfrom its second addresses, then point the name back at your old server — at your DNS provider replace the CNAME with its A record (or, if the domain's DNS is in mesh-net, on the domain's page). The devices return to it once the new record reaches their DNS caches.
If something goes wrong
The script never leaves anything half-done: when it stops with a message, nothing on the server was changed except its log. Its exit messages explain what to do; the most common cases are here.
The script didn't find headscale
Run it on the server where your headscale works. If it is there but installed in an unusual way, the script asks
for the path to its config — type it, for example /etc/headscale/config.yaml, and press Enter. If headscale
runs in docker and keeps its database inside the container (without a mounted folder), find db.sqlite on
the server and give its path instead.
“Your headscale is …” — the version doesn't fit
The import takes databases of headscale 0.23 to 0.28. Older: update headscale (to 0.28 if you can), start it once — it updates its database by itself — and run the script again. Newer than 0.28: mesh-net can't load it yet; write to support.
The data is in PostgreSQL
The import takes only a SQLite database, and there's no ready tool to convert headscale's data. Instead, create a network in mesh-net and add the devices to it with Add device: they get new addresses, everything else works the same.
“Neither sqlite3 nor python3”
Without these programs the database can be copied only while headscale is stopped for a few seconds — the script asks first. Sometimes headscale doesn't close its database cleanly when it stops; the script then starts and stops it once or twice more. If that doesn't help, or headscale isn't run by systemd or docker (then the script can't start it again and won't stop it), install sqlite3 and run the script again:
sudo apt install sqlite3
mesh-net refused the file
- Upload an archive made by Export (.tar.gz) or the db.sqlite file of a headscale server. — another
file was chosen. Pick
mesh-net-import-….tar.gz. - The file is too big: the limit is 64 MB. — the script warns about this on the server already; write to support.
- Your server hands out addresses from 10.20.0.0/16, outside the ranges of mesh-net networks (100.64.0.0/10, fd7a:115c:a1e0::/48): the devices would lose their addresses. — such a network can't keep its addresses in mesh-net; create a new network and add the devices to it.
- The devices nas and laptop have the same address 100.64.0.7. — two devices share an address on your server; delete one of them there (and add it again later in mesh-net), then pack again.
- The database has no schema version: it comes from headscale older than 0.23. Update your headscale, start it once and copy the database again. — see the version.
- Full device names of your server end with example.com (dns.base_domain), and the address of the new network office.example.com is that name or lies under it: headscale can't work like that. Pick another address for the network. — the address you picked lies under the ending of your server's device names; pick another one in Address of the new network.
The full list is in What the import accepts.
A device doesn't come online
- Check that the command ran on that device and that the address after
https://is the network's address from its page. - tailscale printed
requires mentioning all non-default flags— run the line it printed, see above. tailscale statussaysLogged outor asks to log in — add the device again with Add device. More checks: A device shows offline.
Go back to your server
Changed your mind while the old server's data is still there? Start it again if you stopped it
(sudo systemctl start headscale, in docker — sudo docker start headscale) and on every moved
device run the same command with your own server's address — the server_url of your headscale:
sudo tailscale down
sudo tailscale up --login-server=https://SERVER-NAME
Then delete the network in mesh-net.