KLYRN
KLYRN Web
All documentation
  1. Home
  2. Docs
  3. From a Linux server

A Linux server, over SSH

KLYRN copies a document root and its databases while the site keeps serving, then takes the difference at cut-over.

Before you start

An SSH login
Any normal shell for one site. Root, or sudo without a password, lets Migrate read the whole machine first and propose sites for you to map.
A password or a private key
The whole PEM block. Encrypted private keys are not supported yet.
The document root
Absolute, or relative to the SSH user's home. public_html is the default.
The domain name
Required.
Database credentials
Usually read from the site. Otherwise --db name:user:password[@host[:port]], repeatable.
Room here
“less than 1 GB free on this server; free space before migrating” refuses the start.
klyrn import ssh --host old.example.com --user deploy --key ~/.ssh/id_ed25519 \
  --mode files --domain example.com --path public_html

klyrn import ssh --host old.example.com --user deploy --password '…' \
  --mode files --domain example.com --db acme_wp:acme_wp:secret

klyrn import ssh … --transfer rsync --compress zstd-3
--transfer
auto, rsync, tar or manifest.
--compress
auto, none, zstd[-N] or gzip[-N].
--port
22 by default.
--fingerprint SHA256:
Pins the host key.
--engine legacy
One tar stream into a work directory. The default makes two passes straight into the site.

What migrates

The document root
Everything under it, by the ownership rules of every migration. Symlinks pointing outside the tree are counted and reported.
Databases
Found in wp-config.php and .env under the document root and one level above it, or given with --db. Each dump is --single-transaction --quick --skip-lock-tables --default-character-set=utf8mb4 --routines --triggers --events.
Database users
Recreated with the same password. A WordPress wp-config.php is re-pointed, and the original is kept as wp-config.php.klyrn-import.bak.
The crontab of the login
Read with crontab -l, and again at the final sync. Root's crontab is not read.
The PHP version
Asked of the source's php binary. Otherwise this server's default.

The transfer strategy is measured:

rsync, when both ends have it
Files already here are skipped, and a half-copied file resumes. The final sync also deletes what is gone on the source.
manifest
A size-and-mtime manifest from the source's find is compared with a walk here. The difference travels as one tar stream.
tar
No delta, and nothing deleted on the source is removed here.
Compression
None over roughly 200 MB/s, more on a slower link, capped when either side has fewer than three threads.

What does not migrate

  • Mail. Mailboxes are not migrated from a server without a panel. KLYRN never changes an MX record.
  • Anything outside the document root. Naming a Laravel public/ leaves the code above it behind.
  • The web server's configuration and certificates. Virtual hosts are read to find sites. See what happens to .htaccess.
  • System cron. /etc/cron.d and other users' crontabs are listed and not imported.
  • Virtual hosts past the first 250. They are listed without their application or size.

Downtime

klyrn import apply <id>      # pass 1: the pre-copy. The source stays live.
klyrn import state <id>      # every phase, with its duration
klyrn import sync <id>       # pass 2: the delta. This is the downtime.

In klyrn import state the final sync's line ends with (this is the downtime). Its length depends on:

  • Changed files, sent by the transfer strategy.
  • Databases. Each is snapshotted again and re-applied in full.
  • The link, measured with 16 MiB of incompressible bytes over SSH.

klyrn import sync <id> --freeze freezes a WordPress source. It writes .maintenance into the source document root and removes it afterwards, even when the pass fails. Other sources may lose writes made during the final sync. MyISAM tables are warned about by name.

Checks and refusals

The connection check writes nothing and stops at the first step that fails. A machine with no panel is reported as “a Linux server with no control panel”.

Name
“old.example.com does not resolve.” Check the spelling, or use the address.
Port
Refused: nothing listens on that port. Did not answer within ten seconds: a firewall drops the packets. No route: possibly a private network.
Host key
Pin it with --fingerprint SHA256: or in the connection form. A mismatch is refused before the password is offered: “old.example.com:22 presented host key SHA256:, and you pinned SHA256:”. Without a pin, the first key is recorded and a changed key is refused.
Sign in
“SSH authentication failed”. It is never retried. Some servers only allow key logins for root.
Shell
“The login worked but no command could be run.” Use a normal shell.
“no directory /home/deploy/public_html on the source”
Give the website's document root.
“the document root cannot be /home/deploy itself”
Name the folder that holds the website.
“document root is empty on the source”
A warning. Almost always the wrong path.
“no database credentials found in wp-config.php or .env”
Add them with --db, or the site moves without databases.
“access denied for acme_wp@localhost; check the credentials” / “no database named acme_wp”
Found during the analysis, before the copy.
“mysqldump is not available on the source” / “The database server would not answer with this login”
The files can still be copied.
“nginx is installed but its configuration could not be dumped”
The reading uses nginx -T, which usually needs root.
“example.com already exists under account other, and this migration is for acme”
A migration writes only inside its own account.

Cut-over

  1. Pre-copyklyrn import apply <id>. Check the site with the preview and the verification.
  2. Lower the TTLHours ahead. The checklist warns above 300 s.
  3. Final syncklyrn import sync <id>, with --freeze for WordPress.
  4. Read the checklistklyrn import cutover <id> covers files, databases, runtime, HTTP, SSL, cron, DNS and the TTL, then the records to set. Files, databases, runtime and HTTP must not say action required.
  5. Change the recordsBy hand, or klyrn import cutover <id> --apply-dns with a connected provider. It is refused before the final sync.
  6. WatchEvery 30 seconds for 15 minutes by default, 60 at most: the page, new 5xx lines in the access log, the certificate and each database. DNS is never rolled back.
  7. Keep the old server for a weekLeave MX alone while that server still carries mail.

Rollback

Nothing is installed on the source and nothing is deleted from it.

  1. Point DNS backThe old machine is as it was.
  2. After a frozen sync that stopped badlyDelete .maintenance in the source document root.
  3. Remove what landed hereklyrn import delete <id> removes the migration record and its working files. It refuses while a job is running. The site and databases are deleted separately.

Troubleshooting

The pass stopped part way
klyrn import resume <id> continues from that phase. Files already here are not sent again.
“the SSH login for is no longer held (klyrn-core restarted)”
Add --password or --key to resume or sync.
“no password is held for acme_wp@localhost”
klyrn import resume <id> --db acme_wp:acme_wp:PASSWORD
“the dump ended early (no ‘Dump completed’ trailer)”
That database is retried on its own.
“3 of 7 database(s) failed; the others are in place”
Resuming copies only the ones that failed.
“rsync is not on both ends; using the manifest strategy”
A warning. Resume within one very large file is lost.
“zstd is not on both ends; using gzip -1”
Also a warning.
“some files changed while they were being read”
tar's exit code 1 on a live source. The final sync picks up the changes.
The source dropped the connection during the handshake
Usually sshd's MaxStartups, past ten unauthenticated connections. KLYRN retries after 1 s, 3 s and 7 s. Resume, or raise MaxStartups.
Discord WhatsApp