What to expect from a migration
Read this before you move anything. It says what KLYRN carries across, what it only reports, what “verified” means, and what has not been tested yet.
The thing to know first
A production DirectAdmin server has been migrated over SSH: 40 accounts, 48 domains and 48 mailboxes. No backup archive produced by a real cPanel or DirectAdmin server has been fed to KLYRN's parsers yet. Every fixture in the test suite was written from those vendors' published documentation, by the same people who wrote the parsers. Such a fixture tests a parser against its author's belief about the format.
What has been done: the parsers, the .htaccess translation and
the pre-cutover report all have tests, and the tests pass. Every route has been run end
to end on a test server against a stand-in source. The stand-in produces a genuine
archive layout and answers the same commands. That exercises SSH, the root check,
detection, invocation, streaming, cleanup and the whole pipeline. It does not exercise
cPanel.
A real archive would cover gaps a hand-written fixture cannot:
- Encoding and line endings. A decade-old server carries byte-order marks, CRLF, a Latin-1 comment in a config file.
- Scale. Hundreds of addon domains, thousands of files, a multi-gigabyte dump, hundreds of grant lines.
- Version drift. Both vendors change these formats between major versions.
- Damaged archives. A backup interrupted halfway, or metadata naming a domain with no matching file. The parsers are written to degrade and never guess. That claim is untested against real damage.
- Character set and collation in database dumps, where a migration can corrupt text without failing.
Anything a parser does not understand becomes a warning on the plan, never a silent omission.
Keep the old server running. Check the imported copy on the new server before you touch DNS, and leave the source alive for a week afterwards.
What is carried across
- Files
- The document root and everything under it, handed to the destination account. The owner is set, setuid and setgid bits are stripped, and symlinks are copied as links and never followed. An archive is read as real tar headers before anything running as root touches it. A path that escapes the directory is refused by name. So is a symlink or hard link whose target leaves it, a device node, a named pipe or an unsupported member type.
- Domains
- The primary domain, and the addon, parked and subdomain entries the source records, each with its own document root and PHP version.
- PHP version
- Mapped from the source's own notation to a version KLYRN has. A domain set to inherit resolves from its parent.
- Databases and their users
- The dumps are restored and the users recreated with their original password hashes. The credentials already in your application's configuration file keep working unchanged. Grants are parsed, including quoted and backtick-escaped database names.
- Scheduled tasks
- The crontab, rewritten to run as the new account's user.
- The application's shape
- Each document root is identified as WordPress, Laravel, plain PHP or static, and the site is created as that type.
Nothing is written until you have read the plan.
klyrn import show <id> lists every domain, database, database user
and cron entry with its conflicts and warnings.
klyrn import apply <id> is the first thing that creates anything. It
takes --domains, --databases, --no-cron and
--no-ssl if you want less than all of it.
What is only reported, never moved
- KLYRN hosts mail. From cPanel, DirectAdmin and Plesk, mailboxes are copied with their existing passwords. From cPanel and DirectAdmin their forwarders are copied too. Autoresponders are named in the job log and not copied. Your MX records are not touched, and mail copied here is not deleted once they point here.
- DNS zones
- When this server is the nameserver, a migrated domain's zone is copied with only its SOA and NS records replaced. Otherwise you get a cut-over list at the end of the import: exactly which records to change, and where.
- Anything the source will not give up
- A missing document root, or one that is the home directory itself, is reported and never guessed at. Anything the parser does not understand becomes a warning on the plan.
- Apache behaviour nginx does not have
- Listed, per directive, so you can decide. See below.
The mail trap after a cut-over is an MX record pointing at the new server. KLYRN runs no mail server, so it would refuse every message. The verification report checks for exactly this and marks it as action required, naming the record.
What happens to .htaccess
nginx does not read .htaccess. KLYRN reads it for you, and
every directive line is classified exactly once, so nothing is
silently dropped. There are three outcomes:
| Classification | What happens |
|---|---|
| Converted | Turned into nginx that KLYRN generates, tested with nginx -t before it is ever loaded, and shown to you before it is applied. It is applied only when you say so. |
| Needs review | Understood, but a person should read it before it is translated. Listed with the file and line it came from. |
| Not possible | nginx has no equivalent. Listed, so you know what stops working before you cut DNS. |
Three shapes are handled specially:
- A WordPress front-controller block is not converted. KLYRN's own vhost already does it.
- Forced HTTPS is recognised as already done, so no second redirect is added to the one KLYRN issues.
- www and bare-name redirects are converted only when this server answers for the target. A redirect to a name this machine does not serve would loop after the cut-over.
The scan is bounded: up to 40 .htaccess files, six directories deep,
256 KB each. A site with more than that gets the first 40, and the report says so.
The report also catches the two ways a conversion goes stale. One is nginx still
serving rules from an .htaccess that has since been deleted. The other is
an .htaccess that was edited after it was converted. Both are reported as
action required.
The preview: seeing it before DNS moves
The imported site is live on the new server the moment apply finishes, and
nothing points at it yet. The preview asks the new server's own nginx for the site with
the site's Host header, which is what a visitor gets once DNS points here.
It does that for every name the site claims: the primary, www if the site
serves it, and each alias and redirect.
Redirects are not followed, so you see where each name sends people.
You also get the lines to do the same from your own machine:
curl -sS -I --resolve example.com:443:203.0.113.10 https://example.com/
With the hosts-file line for the same names you can browse the migrated site in a real browser while the old one still serves the public.
What the preview is not. It is a request to the new server. It does not fetch the old one, and it cannot tell you the two pages look the same. Compare them yourself: a blank page returns HTTP 200.
What “verified” means
The verification report is eleven checks. Each returns passed, warning or action required and carries the evidence that produced it. The whole report can be copied as text for a support ticket. The overall result is the worst row.
| Check | What it establishes |
|---|---|
| Files | The document root exists, has files in it, and they belong to the account. Files still owned by root mean an archive was unpacked in the wrong place, and the site half works. |
| Databases | The databases are here, with their table counts, and the application's own credentials still open one. The check reads wp-config.php and connects with what it finds. |
| Domains | Every name the site claims is in this site's own vhost and does not fall through to the default server. |
| Runtime | The thing that runs the site is running, and ran. This catches a PHP site serving its own source as text. |
| HTTP | What the primary name returns from this server, with the timing. |
| SSL | Whether there is a certificate and which names are on it. No certificate before the cut-over is normal, and the row says which of the two it is. |
| Redirects | Each redirect proved by asking for it. |
| Cron | The scheduled tasks that came across, and the ones attached to the account and to no site. |
.htaccess | Every directive accounted for, and whether the conversion has gone stale. |
| DNS | Only when you ask for it: where each name resolves today, against this server's address. Without asking, the row says “not checked”. |
| Where the MX records point, and an action if one of them points here. |
Every one of those checks runs against the new server. The verification does not connect to the source and does not diff the two sites. It cannot tell you a page renders the same as it used to. It proves the new server is internally consistent and answering. Compare the two yourself while the old one is still up.
The cut-over, in order
- Import and applyThe sites, databases and cron entries exist on the new server. Nothing points at it.
- Preview and verifyLook at the imported site with the curl and hosts lines. Read the report. Fix what it names.
- Lower the TTL at your DNS providerDo it hours before the cut-over. Skip it and the change can take a day.
- Change the recordsThe cut-over list names them. If a connected provider holds the zone,
klyrn dns pointwrites them. - Let the certificate arrive
klyrn site ssl example.com, or wait: the scheduler retries every ten minutes once the name resolves here. - Re-run the verification, this time asking for DNSThe DNS row now has a result, and the SSL row should have gone from “expected” to passed.
- Keep the old server for a weekLong enough for every cached record to expire and for anything you missed to surface.
What happens to the old server
Nothing is installed on it, and nothing is left behind. KLYRN connects over SSH, runs the tools the server already has, streams the result, and deletes any temporary archive it created there. With root it uses cPanel's own packaging tool. Without root, a cPanel account can package itself through cPanel's own API. On a server with no panel, an ordinary Unix login is enough for the files, the databases and the crontab.
- The SSH credential exists only for the length of the job and is never written to the database. Only the local archive path is stored.
- Pin the host key with
--fingerprint SHA256:. A key that does not match stops the connection during the handshake, before the password is offered, and the refusal names both fingerprints. Without it, the key is recorded on the first connection and enforced on every connection after. Encrypted private keys are not supported yet.
The old server keeps serving throughout. If the plan looks wrong you have lost an upload and nothing else.
The commands are on Migration. Each source has its own guide with its refusals and rollback: DirectAdmin, cPanel/WHM, a Linux server over SSH, another KLYRN server.