Slot 42 Cross-panel migration
cPanel to DirectAdmin Migration: Gotchas Official Docs Skip
DirectAdmin imports cPanel accounts, but it renames your databases, ignores wrongly named archives and refuses mdbox mail. Here is what actually breaks.
DirectAdmin will import a cPanel account, and the import will usually report success. Then the sites throw database connection errors, half the mailboxes are empty, and DKIM stops validating. None of that is a bug. The two panels differ in ways the importer never tries to reconcile, and those differences are yours to clean up.
I have done a lot of these. Here is what actually bites, in the order it bites.
The supported path
DirectAdmin reads cPanel move archives natively. You generate them the normal way on the cPanel side:
/scripts/pkgacct olduser /home/backups
The home directory goes in by default, so there is nothing to switch on. --skiphomedir is the flag that leaves it out, and it takes no value. pkgacct matches option names strictly, so --skiphomedir=0 does not read as “include the home directory”, it reads as an option the script has never heard of, and you get the help text instead of an archive.
Copy the result to the DirectAdmin server, drop it in the admin backup directory, then restore at the Admin Level → Admin Backup/Transfer → Restore Backups screen, choosing the local path.
The restore itself is queued, not immediate. If nothing appears to happen, run the task queue in the foreground and watch it:
/usr/local/directadmin/dataskq d200
tail -f /var/log/directadmin/errortaskq.log
That log is where the real failures show up. The web interface will tell you the restore completed while errortaskq.log is full of the reason it did not.
Gotcha 1: the archive filename is a hard requirement
The archive must be named cpmove-<user>. That is the pattern DirectAdmin recognises as a cPanel move file. A backup-11, which is exactly what cPanel’s scheduled backups produce, is not recognised. The restore screen will either not list it or will try to parse it as a DirectAdmin backup and fail.
/scripts/pkgacct already produces the right name, so if the source server is still up, generate a fresh archive and stop there. It is minutes of work and it is always right.
If the source is already gone and a scheduled backup is what you have, renaming it is the whole fix. DirectAdmin’s own migration documentation treats it that way and hands you the loop, which pulls the username out of the timestamped name and leaves the contents untouched:
Do it in the directory the restore screen reads, which is /home/, and hand the files to the admin user while you are there or the interface will list nothing:
mkdir -p /home/admin/all_backups
cd /home/admin/all_backups # the archives you copied over live here
for i in backup-[0-9]*.*.tar.gz; do
[ -e "$i" ] || continue
user=$(printf '%s' "$i" | cut -d_ -f3 | cut -d. -f1)
mv -v "$i" "cpmove-${user}.tar.gz"
done
chown -R admin:admin /home/admin/all_backups
Nothing is unpacked and nothing is repacked. What is inside is the cPanel archive DirectAdmin already knows how to read; the filename is the only thing its importer keys on, and the documentation puts it bluntly: if you have user.tar.gz instead of cpmove-user.tar.gz, it will not work.
For a single account there is also a documented converter that turns a cpmove archive into a DirectAdmin-format backup ahead of the restore, which is worth reaching for when one account is misbehaving and the rest went in fine:
/usr/local/directadmin/shared/cpanel_to_da/cpanel_to_da.sh \
/home/admin/all_backups/cpmove-olduser.tar.gz /home/admin/converted_user_backup/
chown -R admin:admin /home/admin/converted_user_backup
Gotcha 2: the database prefix rename
This is the one that breaks sites, and the reason most of these migrations get written off as a failure. DirectAdmin enforces that every database and database user belonging to an account carries the account’s username as a prefix. On restore, the importer rewrites the names to match. A cPanel database called olduser_wp owned by olduser_wp arrives on DirectAdmin as newuser_wp owned by newuser_wp whenever the DirectAdmin username is not identical to the cPanel one. That happens constantly. You renamed the account, or the old username was longer than the username length DirectAdmin is configured to accept.
Nothing rewrites your application configuration. wp-config.php still asks for olduser_wp, and MySQL now says that database does not exist.
Find every occurrence before you go looking for it one file at a time:
grep -rIl "olduser_" /home/newuser/domains/ 2>/dev/null
grep -rIn "olduser_" /home/newuser/domains/*/public_html/wp-config.php
Fix WordPress with wp-cli, which edits the constants properly instead of guessing at quoting:
cd /home/newuser/domains/example.com/public_html
sudo -u newuser wp config set DB_NAME newuser_wp
sudo -u newuser wp config set DB_USER newuser_wp
sudo -u newuser wp config set DB_PASSWORD 'ThePasswordFromTheImport'
For everything else (.env files, configuration.php, settings.inc.php, raw DSN strings in custom code) a scoped sed is fine, but take the backup suffix:
grep -rIl "olduser_" /home/newuser/domains/ \
| xargs sed -i.bak 's/olduser_/newuser_/g'
Then confirm the account’s databases are what you think they are:
mysql -e "SHOW DATABASES LIKE 'newuser\_%';"
mysql -e "SELECT user,host FROM mysql.user WHERE user LIKE 'newuser\_%';"
The import keeps the passwords, so the credentials in the old config are usually still valid. Only the names moved. Do not regenerate passwords unless you have to. It doubles the number of things you then have to fix.
Gotcha 3: mdbox mail must become maildir before you export
DirectAdmin understands maildir. It does not understand Dovecot’s mdbox format. If the cPanel server was running mdbox, the mail either does not come across or arrives as unusable container files, and you find out after MX has already moved.
Convert on the source, before /scripts/pkgacct runs, and use cPanel’s own script rather than driving doveadm by hand. A hand-rolled doveadm sync writes a maildir somewhere of your choosing and leaves cPanel still pointed at the mdbox, so the account keeps delivering into the old store and pkgacct keeps packing it. cPanel’s script converts the mailboxes and reconfigures Dovecot to read the converted account afterwards, which is the half that matters.
# check what format the account is on
grep -r "mail_location" /etc/dovecot/dovecot.conf /etc/dovecot/conf.d/ | head
# convert every mailbox on the account
/usr/local/cpanel/scripts/convert_mdbox_to_maildir --user=olduser
Use --email=info@example instead if you only want one mailbox. There is a third flag, --delete-old-format, which removes the mdbox files after a successful conversion. Do not reach for it on the same run. The old files sitting there untouched are the only thing standing between a bad conversion and a lost mailbox, and deleting them is a one-line command you can run later, once the mail is verified on the destination.
Verify the result is a real maildir (cur, new and tmp directories with numbered files inside) before you trust it:
ls /home/olduser/mail/example.com/info/
find /home/olduser/mail/example.com/info/cur -type f | wc -l
There is nothing to rerun at cutover, and this is where people waste an hour of a maintenance window. cPanel’s documentation is explicit that after a conversion the script reconfigures Dovecot to access the converted account in maildir format, so everything delivered from that moment on is written as maildir already. Converting is a one-way format change, not a delta sync, and running it a second time has no later mail to find. What you rerun before cutover is pkgacct, so the archive you restore carries whatever arrived since the first one.
If the conversion is messy, or the source server is going away before you can fix it, pull the mail over IMAP instead and skip the file-level path entirely. That is the imapsync runbook approach and it is often less work than arguing with Dovecot.
Gotcha 4: paths change, so anything absolute breaks
cPanel puts the primary document root at /home/ and addon domains inside it. DirectAdmin puts every domain at /home/. Nothing on the DirectAdmin side rewrites paths that were baked into your application, your cron jobs, or your .htaccess.
grep -rIl "/home/olduser/public_html" /home/newuser/domains/
crontab -u newuser -l | grep -n "/home/"
What does not carry over
| Item | Behaviour on DirectAdmin | What to do |
|---|---|---|
| Cron PHP binary paths | /opt/ does not exist |
Rewrite to /usr/ |
| Cron script paths | Point at the old public_html layout |
Rewrite to /home/ |
.htaccess EA4 handlers |
AddHandler application/ is meaningless or fatal |
Remove; set the PHP version in DirectAdmin per domain |
| CloudLinux PHP Selector settings | Not imported | Set the version from DirectAdmin’s PHP selector, from the versions CustomBuild compiled |
| Email filters | cPanel filter format is not read | Rebuild in DirectAdmin; check /etc/ |
| Autoresponders | Format differs, frequently silently absent | Recreate manually |
| Forwarders | Usually import fine | Verify in /etc/ |
| DKIM keys | Not carried across | New key is generated; publish the new TXT record |
| Custom DNS records | Zone is regenerated from DirectAdmin’s template | Re-add TXT, SPF, DMARC, CAA, and any third-party verification records |
| cPanel-only apps | Softaculous licences, cPanel Terminal, etc. | Not applicable; install DirectAdmin equivalents |
The DKIM row is the sleeper. cPanel keeps private keys under /var/. DirectAdmin keeps them at /etc/ and generates a fresh pair. Your published selector record still advertises the old public key, so every signed message fails DKIM until you update DNS. Nobody notices for a day or two, and then a client asks why their invoices are landing in spam.
DNS zone differences
Both panels use BIND and both store zones under /var/named, so it looks like it should be a straight copy. It is not. The DirectAdmin importer builds the zone from its own template using the new server’s nameservers and IP, and anything that was not a standard A, CNAME or MX record gets dropped.
Diff the two before you cut over:
# on the cPanel source
cat /var/named/example.com.db
# on DirectAdmin
cat /var/named/example.com.db
diff <(sort /root/old-zone.txt) <(sort /var/named/example.com.db)
Then re-add the missing records in DirectAdmin’s DNS management and reload:
rndc reload example.com
dig +short TXT example.com @localhost
SSL
The importer does not reliably reinstall the certificates in the archive, and reissuing is faster than working out why. Once DNS points at the new server:
cd /usr/local/directadmin/scripts
./letsencrypt.sh request_single example.com 4096
Do it for www and for every mail hostname the account uses, and check that DirectAdmin’s auto-renew is enabled so you are not back here in 90 days.
Pre-flight checklist
| # | Check | Where |
|---|---|---|
| 1 | Mail format is maildir, not mdbox | cPanel source, before export |
| 2 | DirectAdmin username decided and recorded | Both |
| 3 | Archive named cpmove-<user> |
Transfer |
| 4 | MAXADDON present and non-zero in the user file |
cPanel source |
| 5 | Zone file copied off before import | cPanel source |
| 6 | Old DB names listed for the search-and-replace | cPanel source |
| 7 | Cron jobs exported to a text file | cPanel source |
| 8 | PHP version per domain noted | cPanel source |
| 9 | TTL lowered at least 24h ahead | DNS provider |
| 10 | dataskq d200 run and errortaskq.log clean |
DirectAdmin |
Item 4 has its own failure mode that produces no error at all: the import succeeds and the addon domains are simply missing. That one is covered in why your addon domains vanish.
FAQ
Can I import a cPanel backup taken by cPanel’s own scheduled backup system?
Yes, after renaming it to cpmove-<user>. The contents are compatible and DirectAdmin’s documented procedure is a rename and nothing more; the filename is the only thing the importer keys on.
Does the import preserve MySQL passwords?
Yes, in my experience the grants come across with their existing password hashes. It is the database and user names that change, which is why the search-and-replace is the whole job.
Do I need to shut the source down during the transfer?
No, but you need a second pass. Take the archive, import it, test everything on the new server using a hosts-file override, then rsync the delta and re-run the mail sync immediately before flipping DNS.
The restore said it worked but the account has no websites. What now?
Read /var/ first, then check whether the account has domains at all in /usr/. An empty list with a working primary domain is the MAXADDON symptom.
Is there a way to run the restore entirely from the command line?
The restore is driven through DirectAdmin’s task queue, so you can queue it and then force processing with /usr/. That is also the fastest way to see the errors, since the queue runner prints what the interface hides.
Where this fits
Cross-panel migration is mostly a list of small differences, each of which breaks something quietly. I run these end to end, including the database rewrite and the mail conversion, as server migration work. If the reason you are leaving is the cPanel licence cost rather than the panel itself, the 2026 pricing options post lays out what the alternatives actually cost to run.