Migrating Pangolin to Another Host

Migrating Pangolin to Another Host
Pangolin logo

Why this is easier than it sounds

You can move Pangolin to a new server without re-adding a single site. Almost everything Pangolin knows lives in one folder called config. Move that folder carefully, point your domain at the new server, and your sites reconnect on their own.

The one thing that trips people up is file permissions. I'll show you how to avoid that.

But why would you want to move?

I simply migrated for a better price model. Digital Ocean is fantastic and I enjoy their platform quite a bit, but I found that racknerd provided more resources for much cheaper per month. It lacks some of the advanced features and backup that Digital Ocean provides, but I'm working on adjusting my own backups to solve that issue.

Before you start

You need four things:

  • Your old server, still running Pangolin, and a way to log in to it (like SSH).
  • A new server with Docker and Docker Compose installed. A fresh Ubuntu or Debian VPS is fine. Be sure to follow my other blog on installing docker compose if you're running into issues!
  • Access to your DNS settings, wherever your domain is managed (Cloudflare, your registrar, etc.).
  • About an hour, and a quiet time when a few minutes of downtime won't bother anyone.

Your Newt sites (the little agents running in your homelab) don't need any changes, as long as you keep the same domain.

The steps

In the commands below, ~/pangolin is the folder that holds your docker-compose.yml and config folder. Swap in your own path, and swap you@new-server for your new server's login.

Step 1: Lower your DNS TTL (the day before)

TTL is how long the internet remembers where your domain points. In your DNS settings, set it to 5 minutes (300 seconds) on your Pangolin records. Do this a day early so the change has time to spread. Then, on moving day, the switch to the new server takes minutes instead of hours.

Step 2: Get the new server ready

Install Docker and Docker Compose. Then open these ports in the new server's firewall (and in your VPS provider's firewall, if it has one):

  • 80 and 443 (TCP): normal web traffic
  • 51820 and 21820 (UDP): the tunnels your sites connect through

Check the ports: section of your docker-compose.yml to confirm these match your setup.

Step 3: Stop Pangolin on the old server

cd ~/pangolin
sudo docker compose down

Stopping it first means nothing changes while you copy it. Your sites go offline from here until Step 9. Keep that in mind.

Step 4: Pack everything into one file

Every file on Linux carries a little label saying who's allowed to read it. Pangolin's certificate file, acme.json, is picky: only its owner may read it. If that label changes during the move, Traefik refuses to use the file and your HTTPS breaks.

So don't copy files one by one. Pack them into a single archive that keeps the labels:

cd ~
sudo tar -cpzf pangolin-backup.tar.gz pangolin

The p in -cpzf is the important part. It means "preserve permissions." This is necessary for your acme.json permissions, which will fail on the new host if permissions aren't intact.

Step 5: Copy the archive to the new server

scp pangolin-backup.tar.gz you@new-server:~

Step 6: Unpack it on the new server

cd ~
sudo tar -xpzf pangolin-backup.tar.gz

Use sudo here too. Without it, tar quietly resets the labels, which is exactly what we're trying to avoid.

The rsync way: Steps 4 to 6 in one command

rsync copies a folder straight from one server to another, so there's no archive to pack and unpack. It's faster, but the command looks scary. Here it is, then piece by piece.

First, install it on both servers:

sudo apt install rsync

Then, on the old server:

sudo rsync -a --numeric-ids -e "ssh -i /home/you/.ssh/id_ed25519" ~/pangolin/ root@new-server:/home/you/pangolin/

Piece

What it means

sudo

Run as the admin, so rsync can read acme.json and other locked files

-a

"Archive mode": copy every subfolder and keep permissions, owners and dates. This is the part that protects acme.json

--numeric-ids

Keep file owners by their number, not their name. User names can differ between servers; the numbers are what Docker cares about

-e "ssh -i …"

Use your SSH key. Without this, sudo makes rsync look for the admin's key instead, and the login fails

~/pangolin/

What to copy. The slash at the end means "the stuff inside this folder"

root@new-server:/home/you/pangolin/

Where to put it. Logging in as root lets rsync set the right owners on the other side

Do a practice run first. Add -n ("pretend") and -v ("tell me what you're doing"). rsync lists what it would copy without copying anything:

sudo rsync -anv --numeric-ids -e "ssh -i /home/you/.ssh/id_ed25519" ~/pangolin/ root@new-server:/home/you/pangolin/

If the list looks right, run it again without -n.

Can't log in as root on the new server? Many VPSes block it. Use your normal user and tell rsync to use sudo on the far side instead: swap root@new-server for you@new-server and add --rsync-path="sudo rsync". For this to work, your user must be able to run sudo on the new server without typing a password.

Common rsync errors

Error

What it means

Fix

Permission denied (publickey)

sudo made rsync use the admin's SSH key, not yours

Add the -e "ssh -i …" part with your key's path

rsync: command not found

rsync isn't installed on the other server

sudo apt install rsync on that server

sudo: a terminal is required

--rsync-path="sudo rsync" needs a password on the new server

Log in as root instead, or allow passwordless sudo for your user

Files land in pangolin/pangolin/

No slash at the end of the source folder

Use ~/pangolin/, with the slash

acme.json isn't -rw------- after copying

-a was left out, or rsync didn't run as root on one side

Run Step 7's chmod fix, and check both sudo and the root@ login

In all honesty, I had Claude Code help me out a bit on the rsync piece. I'm learning as I go, too.

Step 7: Double-check the certificate file

ls -l ~/pangolin/config/letsencrypt/acme.json

The line should start with -rw-------. If it doesn't, fix it:

sudo chmod 600 ~/pangolin/config/letsencrypt/acme.json

Step 8: Point your domain at the new server

In your DNS settings, change your Pangolin records to the new server's IP address. That's usually your dashboard record (like pangolin.example.com) and the wildcard record (*.example.com) that your services use.

Step 9: Start Pangolin on the new server

cd ~/pangolin
sudo docker compose up -d
sudo docker compose ps

All the containers (pangolin, gerbil, traefik) should say "running" or "healthy." Give your sites a minute or two to find the new server.

Step 10: Check it all works

  1. Log in to the Pangolin dashboard.
  2. Look at your sites. They should show as online.
  3. Open one of your services in a browser and check for the padlock.
  4. Once you're happy, set your DNS TTL back to normal.

Keep the old server around for a week before you delete it, just in case.

If something goes wrong

What you see

Likely cause

Fix

Certificate warning in the browser

acme.json lost its permissions

Redo Step 7, then sudo docker compose restart traefik

Sites stay offline

DNS still points at the old server, or the UDP ports are blocked

Wait for DNS to update, then check the firewall from Step 2

Dashboard won't load

DNS hasn't switched yet

Wait a few minutes; check with ping pangolin.example.com

Containers keep restarting

Files unpacked without sudo

Unpack again with Step 6, exactly as written

Changed your mind? Stop Pangolin on the new server, point DNS back at the old server's IP, and run sudo docker compose up -d on the old one. Nothing there was touched, so it picks up right where it left off.