Skip to content

Migrate cPanel Accounts to Nginx Without Downtime Using Rsync and DNS TTLs

A step-by-step guide to moving shared hosting accounts from cPanel/WHM to an Nginx stack with zero downtime using rsync, DNS manipulation, and proper testing.

Written by AISali·August 12, 2026·5 min read
Migrate cPanel Accounts to Nginx Without Downtime Using Rsync and DNS TTLs

Why This Migration Matters Now#

Since cPanel's 2019 pricing overhaul, per-account licensing has become a significant margin killer for resellers. A server with 100 accounts now costs $45.50/month just for the panel—a cost that didn't exist five years ago. But the migration itself is the real blocker: nobody wants to risk customer downtime or data loss.

This guide walks through a proven, repeatable process for moving shared hosting accounts from a cPanel/WHM server to a modern Nginx-based stack with zero customer-visible downtime. The approach uses tools you already have: rsync, standard DNS manipulation, and a staging environment. No proprietary migration wizard, no black-box scripts.

What You'll Need Before Starting#

  • A destination server running Nginx, PHP-FPM, and your chosen control panel (or Salieno Core, which handles Nginx vhost provisioning natively)
  • SSH root access to both source (cPanel) and destination servers
  • Enough disk space on the destination to hold all migrated accounts simultaneously
  • Access to your DNS management (registrar, Cloudflare, or your own nameservers)
  • A maintenance window concept—not for downtime, but for the final sync when you freeze writes

Step 1: Lower DNS TTLs 48 Hours Before Migration#

This is the single most important prep step. Log into your DNS provider and set the TTL on every A and AAAA record you plan to migrate to 300 seconds (5 minutes). This ensures that when you flip the DNS later, propagation happens fast.

bash
# If you manage DNS via a script or API, update TTLs in bulk
# Example for Cloudflare API:
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records/RECORD_ID" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"ttl":300}'

Wait at least 48 hours before proceeding. This ensures the old, longer TTLs have expired from resolver caches worldwide.

Step 2: Export Site Structures from cPanel#

You need to know what you're moving. On the source cPanel server, generate a list of all accounts and their document roots:

bash
# List all cPanel accounts and their home directories
for user in $(ls /var/cpanel/users/); do
  home=$(grep ^HOME /var/cpanel/users/$user | cut -d= -f2)
  domain=$(grep ^DNS /var/cpanel/users/$user | head -1 | cut -d= -f2)
  echo "$user|$domain|$home"
done > /root/migration-sites.txt

For each domain, note:

  • Document root (usually public_html)
  • Database names and users (from /var/cpanel/databases/ or mysql -e SHOW DATABASES)
  • Email accounts and forwarders (from /etc/valiases/ and /etc/vdomainaliases/)
  • Cron jobs (/var/spool/cron/USERNAME)
  • SSL certificates (/etc/ssl/certs/ or via AutoSSL metadata)

Step 3: Provision Nginx Vhosts on the Destination#

Before syncing files, create the Nginx server blocks on the destination. A basic shared hosting vhost looks like this:

nginx
server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com/public_html;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm-example.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~* /\.(ht|git) {
        deny all;
    }
}

Create a dedicated PHP-FPM pool per site for isolation. This is non-negotiable for shared hosting security:

ini
; /etc/php/8.2/fpm/pool.d/example.com.conf
[example]
user = example
group = example
listen = /run/php/php8.2-fpm-example.sock
listen.owner = www-data
pm = ondemand
pm.max_children = 5
pm.process_idle_timeout = 10s
php_admin_value[open_basedir] = /var/www/example.com:/tmp

Step 4: Initial Rsync—The Bulk Copy#

Do the first rsync while the source site is still live. This copies 95%+ of the data without any interruption:

bash
rsync -avz --numeric-ids --delete \
  /home/USERNAME/public_html/ \
  DEST_IP:/var/www/example.com/public_html/

Export and import databases separately:

bash
# On source
dump mysqldump --single-transaction --routines --triggers DB_NAME > /tmp/db_name.sql

# Transfer and import
rsync -avz /tmp/db_name.sql DEST_IP:/tmp/
ssh DEST_IP "mysql DB_NAME < /tmp/db_name.sql"

Repeat for every account. This step can take hours for large servers—that's fine. The site is still live on cPanel the whole time.

Step 5: The Final Sync (The "Freeze" Window)#

This is the only step where precision matters. You need to minimize the gap between the last rsync and the DNS switch.

  1. Put cPanel in read-only mode if possible (disable writes in the application, or use iptables to block HTTP POST requests temporarily)
  2. Run a final incremental rsync—same command as Step 4, but now it only transfers changed files, which takes seconds to minutes
  3. Final database dump and import—same as Step 4
  4. Switch DNS A records to the destination server IP
bash
# Final rsync—should complete in under a minute for most sites
rsync -avz --numeric-ids --delete \
  /home/USERNAME/public_html/ \
  DEST_IP:/var/www/example.com/public_html/

Because you lowered TTLs in Step 1, most resolvers will pick up the new IP within 5 minutes.

Step 6: Verify and Monitor#

After DNS propagation:

  • Check site rendering: curl -H "Host: example.com" http://DEST_IP/ before DNS fully propagates
  • Verify PHP execution: Create a phpinfo() test page, confirm it runs under the correct FPM pool
  • Check error logs: tail -f /var/log/nginx/example.com.error.log
  • Confirm email delivery if you migrated mail—test both sending and receiving
  • Verify SSL: Issue fresh Let's Encrypt certs with certbot --nginx -d example.com

Keep the old cPanel server running for 7 days as a rollback option. If something breaks, you can point DNS back.

Step 7: Cleanup#

After 7 days of stable operation:

  • Decommission the cPanel server or cancel the license
  • Remove the old rsync scripts
  • Restore DNS TTLs to a sensible default (3600–86400 seconds)
  • Document the new server layout for your team

The Payoff#

This process takes a weekend of focused work for a server with 50–100 accounts. The result: no cPanel licensing fees, a faster Nginx stack, and full control over your hosting infrastructure. If you're running Salieno Core on the destination, vhost provisioning and PHP pool management are handled through the panel, which eliminates most of the manual Nginx configuration in Step 3.

The key insight is that rsync and DNS TTLs give you a migration window measured in minutes, not hours. Plan it right, and your customers never notice the move.

Share

0 comments

Loading comments…

More from the blog

Migrate cPanel to Nginx Without Downtime | Rsync + DNS Guide · Salieno Blog