All documentation
  1. Home
  2. Docs
  3. From DirectAdmin

DirectAdmin to KLYRN

KLYRN reads a DirectAdmin server with one command, turns what it finds into a plan you approve, and moves each site with a pre-copy while the source keeps serving. Read what to expect first; this page is what DirectAdmin does differently.

Read this before anything else

No licensed DirectAdmin server has ever been migrated. Everything on this page was built and measured against a fixture DirectAdmin tree: DirectAdmin's documented on-disk layout, reproduced on a disposable Ubuntu box, walked over real SSH by the production shell program character for character. That proves the shell, the SSH plumbing, the streaming parser, the planner and the routing. It does not prove that a real DirectAdmin writes the files the fixture builds.

Four formats DirectAdmin does not publish are therefore assumptions, and KLYRN says so when it relies on one:

  • <domain>.subdomains: one label per line is assumed.
  • <domain>.pointers: three shapes are parsed, and every alias read from it carries the warning “whose format DirectAdmin does not document; it is treated as an alias. Check it”.
  • user.usage: a key that is absent is reported as not measured, never as zero.
  • The backup tarball's internal layout. DirectAdmin documents the archive's naming, location and compression, and not what is inside it.

So: keep the old server running, look at the copy here before you touch DNS, and leave the source alive afterwards. Every part of this path is built for that order.

What you need before you start

An SSH login to the source
Root migrates every account. A login that can sudo without a password is treated the same. Anything else can migrate only what it owns, and the connection check says so before you plan.
A password or a private key
Paste the whole PEM block. Encrypted private keys are not supported yet and are refused with that sentence.
The SSH port
22 unless you say otherwise; anything from 1 to 65535.
Two readable files on the source
/usr/local/directadmin/custombuild/options.conf, which is the only place a domain's PHP version can be resolved from, and /usr/local/directadmin/conf/my.cnf, which is how the databases are listed without a password ever leaving the source. The check reports each one that this login cannot read.
Free space here
A migration refuses to start with “less than 1 GB free on this server; free space before migrating”.
Somewhere for mail to live
KLYRN has no mail stack. Decide this before the cut-over, not after. See below.

There are two ways in, and they are not the same product:

The live server, over SSH
One website at a time, from Migrate in the panel or from the CLI. Give KLYRN the host, a login and the document root and it runs the two-phase engine: a pre-copy while the old server keeps serving, then a delta of what changed, with resume, rate and ETA.
klyrn import ssh --host old.example.com --user root --domain acme.com
Selecting accounts and domains off a whole DirectAdmin server in one pass is not in 0.3.5‑beta.1. That estate walk is in the next release and is not published yet; today, migrate the document roots one at a time.
A DirectAdmin user backup
An archive you already have. One account, all of it after the archive exists, no pre-copy and no delta.
klyrn import directadmin /root/user.acme.tar.gz

What migrates

Accounts and their domains
The walk reads /usr/local/directadmin/data/users: every account, its domains, its subdomains and its pointers. One shell command for the whole server, streamed, so the first account appears while the rest are still being read, and run at lower CPU and IO priority where the source has the tools for it, because the source is still serving.
A PHP version per domain, never one copied across the account
DirectAdmin stores an index, not a version: php1_select=2 in the domain's configuration, resolved through custombuild/options.conf as php2_release=8.1. A single account routinely runs 8.2 on its main domain and 5.6 on a legacy site, and each carries its own.
Files
The document root, copied by rsync when both ends have it and by a size-and-mtime manifest when they do not. Files land owned by the destination account, setuid and setgid bits are stripped, and symlinks stay symlinks and are never followed.
Databases
Listed by pointing the client at DirectAdmin's own conf/my.cnf with --defaults-extra-file, so the source's da_admin password is read on the source and never leaves it: not into KLYRN's memory, not into a job log, not into an argument ps could show. Each dump is --single-transaction --quick --skip-lock-tables --default-character-set=utf8mb4 --routines --triggers --events.
The application's own credentials
The discovery deliberately reads no database password. The engine finds the site's own credentials in wp-config.php or .env under the document root at analysis time, and recreates the user with the same password, so the file that is already there keeps working.
Scheduled tasks: with one exception worth reading
The engine copies the crontab of the login it connected with, rewritten to run as the new account's user. It does not read root's crontab, which belongs to the machine rather than to an account, so a migration run as root, which is how a whole DirectAdmin server is read, carries no cron entries. The inventory counts each account's entries, so you know how many to recreate.

A version this server does not offer is warned about before anything moves: “runs PHP 8.6, which this server does not offer. The nearest is 8.5: close, but not the same interpreter, so test the site before you cut over.” A tie goes to the older version, because legacy code survives being moved back a minor far more often than forward. A slot that cannot be resolved at all stays unknown and the plan says so, rather than becoming this server's default in silence.

PHP past end of life is offered, and said once. 5.6, 7.0 to 7.4, 8.0 and 8.1 are named in one sentence on the plan: KLYRN will run them so the sites keep working, and they no longer receive security fixes. A panel that cannot run the site being migrated cannot migrate it.

From an archive, the same bundle is built by reading the layout directly:

backup/user.conf                 username=, domain=, email=, package=, ip=
backup/crontab.conf              <id>=<schedule> <command>
backup/<user>_<db>.sql           one dump per database
backup/<user>_<db>.conf          <dbuser>=passwd=<hash>&accesshosts=localhost
backup/<domain>/<domain>.db      the zone, read for the record
backup/<domain>/email/passwd     mailboxes: counted, never migrated
domains/<domain>/domain.conf     php1_select=, ssl=
domains/<domain>/public_html/    the document root
domains/<domain>/subdomain.list  one subdomain label per line
domains/<domain>/pointers.conf   <alias>=alias|pointer

A database user whose stored hash begins with * is recreated with that hash, so the application's own credentials keep working. A password in any other form is dropped and a new one is generated, and the job log says which user that happened to.

What does not migrate

Mail does not migrate. KLYRN has no mail stack. Mailboxes, forwarders, autoresponders and vacation messages are counted during the walk, listed on the plan, and left on the source. The connection check says it, the inventory says it, and the plan says it in the plainest words the product has: “996 mailbox(es) and 497 forwarder(s) are NOT part of this migration and stay on the source. When it finishes, it finishes as Complete with warnings, not Complete.” Your MX records are not touched by anything on this page.

The rest of what is read and not taken, as the plan lists it:

  • FTP accounts. KLYRN uses SFTP with the account's own login; DirectAdmin's extra FTP users are not recreated.
  • DNS zones are read for the record, not taken over. DirectAdmin's own nameservers keep answering until you move the delegation.
  • Reseller structure. Accounts keep their owner in the inventory, but KLYRN creates them as accounts; a reseller's limits and packages are not recreated.
  • Apache and nginx customisations (.cust_apache, .cust_nginx, .cust_httpd) are not translated.
  • Certificates from the archive are recorded and not installed. KLYRN issues its own once the name resolves here.

One thing KLYRN will not even try to read: the DirectAdmin version. DirectAdmin publishes no version file and the only documented way to get the number is to run its binary, which a connection check does not do. The check says where the number lives instead: run da version on the source yourself.

What downtime to expect

Over SSH the copy happens in two passes. The pre-copy runs while the source is still serving customers and takes as long as it takes; nobody is waiting on it. The final sync is the second pass, and its wall time is the downtime. KLYRN says so where you start it:

klyrn import sync <id>
Final sync of import #7 (job #31). Its wall time is the downtime.

Three things decide how long that is, and no number here would be honest:

  • What changed since the pre-copy. rsync sends only the difference and deletes what is gone; the manifest strategy compares size and mtime and sends only what differs.
  • The databases, in full. There is no safe incremental strategy for InnoDB without binlog access on the source, which a migration never has. The final sync takes a fresh snapshot and re-applies it, so a large database is copied twice across the migration as a whole.
  • The link. It is measured before anything moves (16 MiB of incompressible bytes over SSH) and the compression level is chosen from the result, with the reason recorded on the migration.

klyrn import state <id> prints every phase with its duration, and the final sync's line ends with (this is the downtime). That is a measurement of what happened, not a promise made beforehand.

A WordPress source can be frozen for the final sync. klyrn import sync <id> --freeze writes a .maintenance file into the source document root and removes it afterwards, including when the pass fails. Anything that is not WordPress has no standard switch, and the job warns that writes during the final sync may be lost rather than pretending otherwise. A database with MyISAM tables gets its own warning: the snapshot cannot be transactional for them.

The archive route has no second pass. The account is packaged, downloaded, analysed and applied, and everything a customer writes on the source after the archive was made is not in it.

The connection check, and what each refusal means

The check writes nothing. No migration row, no job, no history entry naming a migration that does not exist: it is a question, and questions leave no records. It works through the name, the port, the handshake, the login, what the machine is and what the login may do, and it stops at the first of those that fails, because “could not connect” is one sentence for six situations with six different fixes.

Each check, and what its refusal means
CheckRefusal, and the fix in it
Name“old.example.com does not resolve.” Check the spelling, or use the address. A name that has not propagated yet will not resolve here either. Skipped when you typed an IP address.
PortThree different problems, never merged. Refused: something answered, so the address is right and nothing is listening on that port. Did not answer within ten seconds: a firewall is dropping the packets rather than refusing them. No route: the address may be on a private network this server cannot reach.
SSHThe server speaks SSH. A source refusing new connections under load is recognised as busy rather than unreachable, and the migration waits and retries.
Host keyA fingerprint you pin is compared before the credential is offered. See below.
Sign in“SSH authentication failed”: check the password or key; some servers only allow key logins for root. Authentication failures are never retried: retrying one is how a migration locks the account it is migrating.
What it isRead from files that exist, never by running the panel's own programs. A login with a restricted shell fails here: “The login worked but no command could be run.”
What can be readNames whichever of options.conf and conf/my.cnf this login cannot read, and what that costs: PHP versions or databases will be missing from the inventory. Connect as root.
PermissionsRoot, sudo-without-password, or neither. The third is not an error: it means only what this login owns can be migrated.
What will not be migratedAlways shown for a DirectAdmin source, before anything is planned: mail is counted and stays on the source.

Give the host key's fingerprint and it is checked during the handshake. A server whose key does not match is refused before the password is offered, because a credential must not be handed to a machine that is not the one you meant. The refusal names both fingerprints: “old.example.com:22 presented host key SHA256:…, and you pinned SHA256:…”

On 0.3.5‑beta.1 the pin is the fingerprint you give, and nothing else. A connection with no fingerprint trusts whatever key answers, so pin one for any source you will connect to more than once. Recording the key on the first connection, refusing a key that has changed since, separating “seen” from “approved”, and a page in the panel listing the record are all built. They arrive with the database migration after this release, and this page will say so when they land.

Two more things are decided later, when you press Start rather than when you connect:

  • A site that already exists here. “3 of the selected sites already exist here: …”. One unresolved collision stops the whole migration, because one that overwrote a live site would be worse than one that would not start.
  • A database name that is taken. Not a refusal: the plan gives it a suffixed target name, so two DirectAdmin servers can be merged onto one KLYRN without the second silently overwriting the first.

DirectAdmin does not record which site uses which database. A database no site's configuration named is attached to that account's main site, and the plan says exactly that, with the names, so you can move it if it belongs somewhere else.

The cut-over

The sync and cut-over commands below belong to the live-SSH route. An account imported from a DirectAdmin archive has no second pass, so for that route the window is between the archive being made and DNS moving.

  1. Decide what happens to mail, firstKLYRN never changes an MX record and runs no mail server. If MX points at the old server, leave it there and leave that server answering. If you are moving to a mail provider, do it before the websites, while the old server is still the fallback. An MX record pointing at KLYRN after the cut-over means every message is refused.
  2. Pre-copy, then look at the siteThe site is live here the moment the pre-copy finishes and nothing points at it. Use the curl --resolve and hosts lines the report hands you.
  3. Lower the TTL at your DNS providerHours before the cut-over, not at it. The checklist warns when a record's TTL is over 300 s and says how long visitors may keep reaching the old server.
  4. Run the final syncklyrn import sync <id>. This pass is the downtime.
  5. Read the checklistklyrn import cutover <id> prints files, databases, runtime, HTTP, SSL, cron, DNS and the TTL, each with its evidence, then the records to change. It refuses to call itself ready while files, databases, runtime or HTTP say action required.
  6. Change the recordsBy hand, or klyrn import cutover <id> --apply-dns when a DNS provider is connected for that zone. Switching DNS before the final sync is refused: it would send visitors to the pre-copy.
  7. WatchA bounded watch samples the site every 30 seconds (the page as a visitor gets it, new 5xx lines in the access log, the certificate, and each database) for 15 minutes by default and 60 at most. It never rolls DNS back: only you can weigh a broken new site against a broken switch-back.
  8. Keep the old server for a weekLong enough for every cached record to expire, and for the mailboxes that are still on it.

Mail again, because this is where it bites. The verification report checks where the MX records point and marks one pointing at this server as action required, naming the record. That check exists because it is the single most expensive mistake available at a cut-over.

How to roll back

KLYRN never deletes or suspends anything on the source. Nothing is installed on it either. The migration connects over SSH, runs read-only commands the server already has, and copies. That is what makes rollback possible, and it is true on every route on this page.

So rolling back is undoing what happened here, and putting the names back:

  1. Point DNS back at the old serverThe records you changed, changed back. The TTL you were on decides how quickly that takes effect, which is the other reason to lower it beforehand.
  2. Leave the old server aloneIt has been serving the whole time. It has every file and every mailbox it had before the migration started.
  3. Remove what landed here, when you are readyklyrn import delete <id> removes the migration record and its working files. It does not delete the sites and databases the migration created (that is a separate, deliberate act) and it never removes an archive you supplied by path.

If the plan looked wrong before you applied it, you have lost the time the analysis took and nothing else: nothing exists on this server until apply.

Troubleshooting

“the database server would not accept DirectAdmin's own credentials, so no database is listed. This is NOT the same as having no databases.”
The walk tests the connection before it lists anything, precisely so this cannot be read as an empty server. Connect as root, or check that conf/my.cnf is readable and that the credentials in it still work.
The source dropped the connection during the SSH handshake
Usually sshd's MaxStartups under load: beyond ten unauthenticated connections in progress it starts dropping them, and a busy server is exactly the kind being migrated away from. KLYRN waits 1 s, 3 s and 7 s and tries again, then says so. Wait a moment and resume, or raise MaxStartups on the source.
“the SSH login for … is no longer held (klyrn-core restarted)”
Credentials live in memory for the life of the migration and are never written to the database. That is the honest cost of not storing them. Give the password or key again: klyrn import resume <id> --password … or --key …. Nothing already copied is sent again.
“no password is held for …”
A credential KLYRN discovered in wp-config.php or .env is forgotten on a restart like any other. KLYRN re-reads it from the same file it found it in when it can; when it cannot, the message carries the exact command: klyrn import resume <id> --db name:user:PASSWORD.
A domain resolved to no PHP version
Either options.conf could not be read, or the slot the domain is on has no php<N>_release, or that slot is not built on the source (php<N>_release=no). The plan names the domains and says they will be created on this server's default. Check them before you cut over.
“this archive is not safe to extract”
The archive was read as tar headers, not as a list of names. A member that is a symlink or hard link pointing outside the tree, a character or block device, a named pipe, or a type KLYRN will not extract, is refused, and the reason names the member. Get a fresh archive from the source panel.
Some files changed while they were being read
tar's exit code 1 on a live source. The copy is complete otherwise, and the warning is on the migration. It is one of the reasons the final sync exists.

Every route in one place: Migration. What “verified” proves, and what it does not: What to expect.