> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arct.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Linux Server Migration

> Move applications, data, and services from an existing Linux server.

This guide copies application data, databases, and service configuration from your current Linux server to an Arct Cloud server. Read the [migration overview](/migration/overview) first.

<Note>Copy application data and configuration, not the whole root filesystem. See [why a clean target beats a disk clone](/migration/overview#choose-your-approach).</Note>

<Warning>Stop or pause any service that writes while you copy its data. Anything written after the copy starts is left behind, and the gap is easy to miss until the new server is already live.</Warning>

## Deploy the Target Server

Deploy a Linux server with at least the disk and memory the source server uses. See [Deploy a Server](/compute/virtual-machines/deploy), then [connect over SSH](/compute/virtual-machines/connect-ssh).

Match the source distribution and major version where you can. Moving Ubuntu 22.04 to Ubuntu 24.04 works, but expect config changes in nginx, PHP, and systemd unit paths.

## Set Up Access Between Servers

Generate a dedicated key on the Arct Cloud server and pull data from the source. Pulling means the source never needs credentials for the new machine.

```bash theme={null}
sudo ssh-keygen -t ed25519 -N "" -f /root/.ssh/migration
sudo cat /root/.ssh/migration.pub
```

Add that public key to `~/.ssh/authorized_keys` for the root user on the source server, then confirm the connection:

```bash theme={null}
sudo ssh -i /root/.ssh/migration root@SOURCE_IP hostname
```

<Warning>Delete `/root/.ssh/migration` and remove the key from the source server once the migration is done.</Warning>

## Capture the System State

Application files are the obvious half. The other half is the account, key, and rule state your app depends on. Bundle it on the source server in one pass so nothing is missed:

```bash theme={null}
sudo tar -czf /tmp/system-state.tar.gz \
  /etc/passwd /etc/group /etc/shadow /etc/gshadow \
  /etc/ssh/sshd_config /root/.ssh /home/*/.ssh \
  /etc/crontab /etc/cron.d /var/spool/cron
```

Firewall rules are readable rather than file-based, so export them alongside, still on the source server:

```bash theme={null}
sudo ufw status numbered > /tmp/ufw-rules.txt
```

Copy both to the new server and unpack the bundle somewhere safe to read from. Do not overwrite the new server's own `/etc/passwd` or `/etc/shadow` with the old ones; use them as the reference when you recreate accounts:

```bash theme={null}
sudo scp -i /root/.ssh/migration \
  root@SOURCE_IP:/tmp/{system-state.tar.gz,ufw-rules.txt} /tmp/
sudo mkdir -p /tmp/old-state
sudo tar -xzf /tmp/system-state.tar.gz -C /tmp/old-state
```

## Install the Same Packages

On the source server, list the packages you installed by hand:

```bash theme={null}
apt-mark showmanual > /tmp/packages.list
```

Copy the list over and install it on the Arct Cloud server:

```bash theme={null}
sudo scp -i /root/.ssh/migration root@SOURCE_IP:/tmp/packages.list /tmp/
sudo apt-get update
xargs -a /tmp/packages.list sudo apt-get install -y
```

On RHEL, AlmaLinux, or Rocky, use `dnf repoquery --userinstalled --qf '%{name}'` and `dnf install`.

## Copy Application Data

Use `rsync` for each directory that matters. Run it as often as you like; only changed files transfer.

```bash theme={null}
sudo rsync -aAXH --numeric-ids --info=progress2 \
  -e "ssh -i /root/.ssh/migration" \
  root@SOURCE_IP:/var/www/ /var/www/
```

| Flag            | Why                                                     |
| --------------- | ------------------------------------------------------- |
| `-a`            | Preserves permissions, timestamps, symlinks             |
| `-A -X`         | Keeps ACLs and extended attributes                      |
| `-H`            | Preserves hard links                                    |
| `--numeric-ids` | Copies numeric UID and GID instead of remapping by name |

Repeat for the rest: `/etc/nginx/`, `/opt/`, `/home/`, `/srv/`, and any application-specific paths.

<Note>The trailing slash on the source path matters. `/var/www/` copies the contents; `/var/www` copies the directory itself into the target.</Note>

## Move Databases

<Warning>Never `rsync` a running database's data directory. Dump it instead, or stop the database first.</Warning>

<AccordionGroup>
  <Accordion title="MySQL or MariaDB">
    On the source server:

    ```bash theme={null}
    mysqldump --single-transaction --routines --triggers \
      --all-databases > /tmp/mysql-dump.sql
    ```

    On the Arct Cloud server:

    ```bash theme={null}
    sudo scp -i /root/.ssh/migration root@SOURCE_IP:/tmp/mysql-dump.sql /tmp/
    sudo mysql < /tmp/mysql-dump.sql
    sudo systemctl restart mysql
    ```

    Application users and grants come across with `--all-databases`. Verify with `SHOW GRANTS FOR 'appuser'@'localhost';`.
  </Accordion>

  <Accordion title="PostgreSQL">
    On the source server:

    ```bash theme={null}
    sudo -u postgres pg_dumpall > /tmp/pg-dump.sql
    ```

    On the Arct Cloud server:

    ```bash theme={null}
    sudo scp -i /root/.ssh/migration root@SOURCE_IP:/tmp/pg-dump.sql /tmp/
    sudo -u postgres psql -f /tmp/pg-dump.sql postgres
    ```

    `pg_dumpall` includes roles and passwords. Match the major PostgreSQL version on both sides.
  </Accordion>

  <Accordion title="MongoDB">
    ```bash theme={null}
    mongodump --out /tmp/mongo-dump
    ```

    Copy the directory across, then restore:

    ```bash theme={null}
    mongorestore /tmp/mongo-dump
    ```
  </Accordion>

  <Accordion title="Redis">
    Trigger a save on the source, copy `/var/lib/redis/dump.rdb` while Redis is stopped on the target, then start it:

    ```bash theme={null}
    redis-cli SAVE
    ```
  </Accordion>
</AccordionGroup>

## Recreate Services and Schedules

Copy your unit files, then reload:

```bash theme={null}
sudo rsync -a -e "ssh -i /root/.ssh/migration" \
  root@SOURCE_IP:/etc/systemd/system/ /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now your-service
```

Recreate local users with the same UID before copying files they own. Read the original UID out of the captured `/tmp/old-state/etc/passwd`:

```bash theme={null}
sudo useradd -u 1001 -m -s /bin/bash appuser
```

Restore each user's crontab from the captured state, then check `/tmp/old-state/etc/cron.d` for system-level jobs:

```bash theme={null}
sudo crontab -u appuser /tmp/old-state/var/spool/cron/crontabs/appuser
```

## Reapply the Firewall

Read the rules you exported to `/tmp/ufw-rules.txt`, then recreate them:

```bash theme={null}
sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
sudo ufw enable
```

On `firewalld`, use `firewall-cmd --list-all` and `firewall-cmd --permanent --add-service=`.

## Verify Before DNS

Point your own machine at the new server without touching public DNS. Add a line to your local hosts file, `/etc/hosts` on Linux and macOS or `C:\Windows\System32\drivers\etc\hosts` on Windows:

```
NEW_SERVER_IP example.com www.example.com
```

Load the site, sign in, submit a form, and check the logs:

```bash theme={null}
sudo journalctl -u your-service -n 100 --no-pager
```

Remove the hosts entry when you are done testing.

## Cut Over

<Steps>
  <Step title="Stop Writes on the Source">
    Put the application in maintenance mode or stop its service so no new data lands on the old server.
  </Step>

  <Step title="Run a Final Sync">
    Repeat the `rsync` commands and re-dump the database. This pass is fast because only recent changes transfer.
  </Step>

  <Step title="Update DNS">
    Change the A and AAAA records to the new IP. See [Website and DNS Migration](/migration/website-dns) for record-level detail.
  </Step>

  <Step title="Watch the New Server">
    Follow the logs and the CPU, memory, and network graphs on the server detail page for the first hour.
  </Step>

  <Step title="Decommission the Source">
    Keep the old server for a few days, then run the [checks before you delete it](/migration/overview#before-you-delete-the-old-server).
  </Step>
</Steps>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Permission denied (publickey) when pulling from the source">
    Confirm the public key is in the source's `/root/.ssh/authorized_keys`, that the file is `chmod 600`, and that `PermitRootLogin` allows key auth in the source's `/etc/ssh/sshd_config`.
  </Accordion>

  <Accordion title="Files arrive owned by the wrong user">
    You omitted `--numeric-ids`, so rsync remapped ownership by name. Recreate the users with matching UIDs, then re-run the sync with the flag.
  </Accordion>

  <Accordion title="The application starts but cannot reach its database">
    Check the connection host in your app config. `localhost` on the old server may have meant a socket path that differs on the new distribution version. Confirm the database is listening with `ss -lntp`.
  </Accordion>

  <Accordion title="rsync fails with 'No space left on device'">
    Run `df -h` on the target. If the plan disk is smaller than the source's used space, resize before continuing.
  </Accordion>

  <Accordion title="SELinux blocks the web server after copying files">
    On RHEL-family systems, restore the file contexts: `sudo restorecon -Rv /var/www`.
  </Accordion>
</AccordionGroup>
