Introduction

cPanel account transfer permission and ownership errors occur when migrating hosting accounts between servers results in incorrect file permissions, broken database connections, missing cron jobs, or non-functional email configurations. cPanel's Transfer Tool uses WHM APIs to package accounts on the source server, transmit encrypted archives to the destination server, and restore account data with original permissions intact. However, transfer failures can occur at multiple stages: archive creation with rsync permission flags, SSH key authentication for transfer, UID/GID mapping between servers, MySQL grant table restoration, DNS zone file transfer, email forwarder and filter migration, or SSL certificate reissuance. Common causes include UID mismatch between source and destination servers, NFS or shared storage permission conflicts, SELinux contexts blocking restoration, disk quota enforcement during restore, MySQL version incompatibility affecting grant tables, Apache configuration differences, PHP handler changes, custom vhost configurations not transferred, DNS TTL not updated, and nameserver delegation not pointing to new server IP. The fix requires understanding cPanel's account packaging format, WHM transfer protocols, permission preservation mechanisms, and post-transfer verification procedures. This guide provides production-proven troubleshooting for cPanel-to-cPanel transfers, WHM-to-WHM migrations, and server consolidation projects.

Symptoms

  • Website shows 500 Internal Server Error after transfer
  • File permissions showing nobody:nobody instead of user:user
  • SSH access denied with correct key
  • MySQL connection refused (user@localhost lacks privileges)
  • Cron jobs missing after transfer
  • Email bouncing with "User unknown in local recipient table"
  • DNS resolving to old server IP
  • SSL certificate showing invalid or expired
  • Website files present but returning 403 Forbidden
  • phpMyAdmin shows "Access denied for user"
  • FTP login fails with "530 Login authentication failed"
  • Apache error log shows "Permission denied: make_httpd_req"
  • suEXEC disabled due to permission issues
  • Awstats/Webalizer not generating statistics
  • Bandwidth statistics not updating

Common Causes

  • UID/GID mismatch between source and destination
  • File ownership not preserved during rsync
  • MySQL grant tables not restored correctly
  • Cron files (/var/spool/cron) not transferred
  • DNS zone files not updated on nameservers
  • SSL certificates not reissued for new IP
  • Email forwarders/filters not migrated
  • Apache vhost configuration differences
  • PHP handler version mismatch
  • SELinux contexts blocking access
  • .htaccess rules referencing old paths
  • Database user host restrictions (old server hostname)

Step-by-Step Fix

### 1. Diagnose transfer permission issues

Check file ownership after transfer:

```bash # SSH into destination server as root

# Check cPanel account home directory ownership ls -la /home/username/

# Expected output: # drwxr-x--- 15 username username 4096 Jan 15 10:00 . # drwxr-xr-x 5 root root 4096 Jan 15 09:00 .. # drwxr-xr-x 2 username username 4096 Jan 15 09:00 public_html # -rw-r--r-- 1 username username 234 Jan 15 09:00 .bashrc

# Check for wrong ownership (common issues): # - Files owned by nobody:nobody # - Files owned by root:root # - Mixed ownership (some correct, some wrong)

# Find files with wrong ownership find /home/username -not -user username -not -group username 2>/dev/null | head -20

# Check specific web directories ls -la /home/username/public_html/ ls -la /home/username/public_html/wp-content/ # WordPress

# Check configuration files ls -la /home/username/.my.cnf ls -la /home/username/etc/ ```

Check MySQL user privileges:

```bash # Connect to MySQL as root mysql -u root -p

# Check user privileges for transferred account SELECT User, Host, authentication_string FROM mysql.user WHERE User LIKE 'username%';

# Expected: username@localhost with proper privileges # Issues: Missing user, wrong host, empty password hash

# Check database grants SHOW GRANTS FOR 'username'@'localhost';

# Expected output: # GRANT USAGE ON *.* TO 'username'@'localhost' # GRANT ALL PRIVILEGES ON username_db.* TO 'username'@'localhost'

# Check if database exists SHOW DATABASES LIKE 'username_%';

# Exit MySQL EXIT; ```

Check cron jobs:

```bash # List cron jobs for cPanel user crontab -u username -l

# Or check spool file directly cat /var/spool/cron/username

# Expected: User's cron jobs from source server # Empty = cron jobs not transferred

# Check system crontab entries for user grep username /etc/crontab ls -la /etc/cron.d/ | grep username

# Check cPanel-specific cron directory ls -la /home/username/etc/cron/ ```

### 2. Fix file ownership and permissions

Restore correct ownership:

```bash # Fix ownership for entire home directory chown -R username:username /home/username

# Verify ownership restored ls -la /home/username/ | head -10

# Check specific directories ls -la /home/username/public_html/ ls -la /home/username/mail/ ls -la /home/username/etc/ ```

Fix permission for web directories:

```bash # Set correct permissions for public_html find /home/username/public_html -type d -exec chmod 755 {} \; find /home/username/public_html -type f -exec chmod 644 {} \;

# WordPress-specific permissions chmod 755 /home/username/public_html/wp-content/ chmod 755 /home/username/public_html/wp-content/uploads/ chmod 644 /home/username/public_html/wp-config.php

# Restrict wp-config.php (security best practice) chmod 440 /home/username/public_html/wp-config.php chown username:username /home/username/public_html/wp-config.php

# Fix .htaccess permissions chmod 644 /home/username/public_html/.htaccess

# Fix upload directories (need write permission) chmod 755 /home/username/public_html/wp-content/uploads/ find /home/username/public_html/wp-content/uploads -type d -exec chmod 755 {} \; find /home/username/public_html/wp-content/uploads -type f -exec chmod 644 {} \; ```

Set secure permissions for configuration files:

```bash # Database configuration (Joomla, custom apps) chmod 644 /home/username/public_html/configuration.php

# Application config (Laravel) chmod 644 /home/username/public_html/.env chown username:username /home/username/public_html/.env

# SSH directory (must be restrictive) chmod 700 /home/username/.ssh chmod 600 /home/username/.ssh/authorized_keys chmod 644 /home/username/.ssh/known_hosts chown -R username:username /home/username/.ssh

# Mail configuration chmod 700 /home/username/mail/ chmod 700 /home/username/etc/

# Fix cPanel-specific files chmod 644 /home/username/.my.cnf chmod 600 /home/username/.mysql_history ```

Use cPanel's built-in permission fixer:

```bash # Run cPanel permission fixer /scripts/fixperms username

# This script: # - Fixes ownership on all user files # - Sets correct permissions for directories (755) # - Sets correct permissions for files (644) # - Restricts sensitive files automatically # - Preserves executable bits on scripts

# Or fix all accounts (after server migration) /scripts/fixperms --all

# Check script output for errors # fixperms started for username # Fixed permissions on 1234 files # Fixed ownership on 567 files # Completed successfully ```

### 3. Fix MySQL database permissions

Restore database user grants:

```bash # Method 1: Use cPanel's restoregrants script /scripts/restoregrants username

# This restores: # - MySQL user accounts # - Database privileges # - Host restrictions from backup

# Method 2: Manual restoration via WHM # WHM > SQL Services > Restore MySQL Grant Tables

# Method 3: Manual SQL restoration mysql -u root -p

# Recreate user if missing CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';

# Grant privileges on user databases GRANT ALL PRIVILEGES ON username_db.* TO 'username'@'localhost'; GRANT ALL PRIVILEGES ON username_blog.* TO 'username'@'localhost';

# Grant specific privileges if needed GRANT SELECT, INSERT, UPDATE, DELETE ON username_db.* TO 'username'@'localhost'; GRANT CREATE, ALTER, DROP, INDEX ON username_db.* TO 'username'@'localhost';

# Apply changes FLUSH PRIVILEGES;

# Verify grants SHOW GRANTS FOR 'username'@'localhost';

# Exit EXIT; ```

Update database connection configuration:

```bash # Check WordPress configuration cat /home/username/public_html/wp-config.php | grep -E "DB_NAME|DB_USER|DB_HOST"

# Update if database name/user changed during transfer # Common changes: # - Database prefix changed for security # - Host changed from localhost to 127.0.0.1 # - User renamed during migration

# Edit configuration sudo -u username nano /home/username/public_html/wp-config.php

# Common settings: define('DB_NAME', 'username_wordpress'); define('DB_USER', 'username_wpuser'); define('DB_PASSWORD', 'secure_password'); define('DB_HOST', 'localhost');

# For remote MySQL (if configured) # define('DB_HOST', 'mysql.server.com'); ```

Fix database connection issues:

```bash # Test database connection as user mysql -u username -p username_db -e "SELECT 1"

# If fails, check: # 1. User exists mysql -u root -p -e "SELECT User, Host FROM mysql.user WHERE User = 'username';"

# 2. User has password set mysql -u root -p -e "SELECT authentication_string FROM mysql.user WHERE User = 'username';"

# 3. User has grants on database mysql -u root -p -e "SHOW GRANTS FOR 'username'@'localhost';"

# Reset password if needed mysql -u root -p -e "ALTER USER 'username'@'localhost' IDENTIFIED BY 'new_password';" mysql -u root -p -e "FLUSH PRIVILEGES;" ```

### 4. Restore cron jobs

Recreate cron jobs from backup:

```bash # Check if cron backup exists from transfer ls -la /home/username/cron-backup/ cat /home/username/cron-backup/crontab.backup 2>/dev/null

# Or check WHM transfer logs for cron status grep -i cron /usr/local/cpanel/logs/cpbackup/*.log | tail -20

# Restore cron jobs manually crontab -u username -e

# Add cron entries (example format): # m h dom mon dow command

# WordPress cron (every 15 minutes) */15 * * * * /usr/bin/wget -q -O - https://example.com/wp-cron.php > /dev/null 2>&1

# Backup database daily at 2 AM 0 2 * * * /usr/bin/mysqldump -u username -p'password' username_db > /home/username/backups/db-$(date +\%F).sql

# Clean old backups weekly 0 3 * * 0 find /home/username/backups -mtime +30 -delete

# Or restore from cPanel backup /scripts/restorecron username ```

Fix cron permissions:

```bash # Set correct ownership on cron spool file chown username:username /var/spool/cron/username chmod 600 /var/spool/cron/username

# Verify cron directory permissions ls -la /var/spool/cron/ # Expected: drwx------ 2 root root 4096 Jan 15 10:00 cron

# Check cron daemon is running systemctl status crond

# Restart if needed systemctl restart crond

# Check cron logs for errors grep username /var/log/cron | tail -20 ```

### 5. Fix DNS and nameserver issues

Update DNS zone files:

```bash # Check DNS zone file on destination server cat /var/named/example.com.db

# Look for A records pointing to old IP grep "A " /var/named/example.com.db

# Update via WHM # WHM > DNS Functions > Edit DNS Zone

# Or edit zone file directly (not recommended while named running) # Use WHM or scripts instead

# Use cPanel script to rebuild zone /scripts/rebuilddnsconfig username

# Or rebuild all zones /scripts/rebuilddnsconfig --alldomains

# Restart named service systemctl restart named

# Check zone loaded correctly dig @localhost example.com SOA

# Expected: New server's hostname in SOA # Check serial number incremented ```

Verify DNS propagation:

```bash # Check DNS from external resolver dig @8.8.8.8 example.com A

# Check all record types dig @8.8.8.8 example.com ANY

# Check nameservers dig @8.8.8.8 example.com NS

# Expected output: # example.com. 300 IN A NEW_SERVER_IP

# If still showing old IP: # 1. Wait for TTL to expire # 2. Update nameserver delegation at registrar # 3. Flush local DNS cache

# Check local DNS cache (if using nscd) systemctl restart nscd

# Or flush browser DNS cache # Chrome: chrome://net-internals/#dns > Clear host cache ```

Update nameserver delegation:

```bash # Update at domain registrar (GoDaddy, Namecheap, etc.) # 1. Log into registrar account # 2. Find domain management # 3. Update nameservers to: # - ns1.newserver.com # - ns2.newserver.com # 4. Save changes

# Or update A records if using registrar DNS: # - @ -> NEW_SERVER_IP # - www -> NEW_SERVER_IP # - mail -> NEW_SERVER_IP

# Propagation time: 24-48 hours globally # Check status: # https://www.whatsmydns.net/ ```

### 6. Fix SSL certificate issues

Reissue AutoSSL certificate:

```bash # Check SSL status in cPanel # cPanel > SSL/TLS > Manage SSL sites

# Or via command line /usr/local/cpanel/bin/check_all_ssl

# Force AutoSSL run for specific user /usr/local/cpanel/bin/autossl_check username

# Or for all users /usr/local/cpanel/bin/autossl_check --all

# Check AutoSSL logs tail -f /usr/local/cpanel/logs/autossl/autossl.log

# Expected output: # Autossl check started for username # Certificate generated for example.com # Certificate installed successfully

# Wait 15-30 minutes for AutoSSL to complete # Certificate valid for 90 days (Let's Encrypt) ```

Install custom SSL certificate:

```bash # If using commercial certificate (Comodo, DigiCert, etc.)

# Upload via WHM # WHM > SSL/TLS > Install an SSL Certificate on a Domain

# Or via command line # Install certificate files cp /path/to/certificate.crt /home/username/ssl/example.com.crt cp /path/to/private.key /home/username/ssl/example.com.key cp /path/to/ca-bundle.crt /home/username/ssl/example.com.ca-bundle

# Set correct permissions chown root:root /home/username/ssl/ chmod 755 /home/username/ssl/ chmod 644 /home/username/ssl/*.crt chmod 600 /home/username/ssl/*.key

# Install via cPanel API /scripts/install_ssl_domain.sh username example.com

# Verify installation /usr/local/cpanel/bin/check_all_ssl ```

Fix mixed content after SSL:

```bash # Update WordPress URLs to HTTPS # cPanel > phpMyAdmin > Select WordPress database

# Run SQL queries: UPDATE wp_options SET option_value = 'https://example.com' WHERE option_name = 'home' OR option_name = 'siteurl';

UPDATE wp_posts SET post_content = REPLACE(post_content, 'http://example.com', 'https://example.com');

UPDATE wp_postmeta SET meta_value = REPLACE(meta_value, 'http://example.com', 'https://example.com');

# Or use WP-CLI sudo -u username wp search-replace 'http://example.com' 'https://example.com' --all-tables

# Clear cache after replacement sudo -u username wp cache flush ```

### 7. Fix email configuration

Restore email forwarders:

```bash # Check forwarders file cat /home/username/etc/example.com/forwarders

# Or check via cPanel API uapi --user=username Email list_forwarders

# Restore forwarders from backup # Forwarders stored in: /home/username/etc/domain.com/

# Recreate forwarder via command line uapi --user=username Email add_forwarder \ domain=example.com \ email=info \ forwarder_email=destination@example.com

# Or add multiple forwarders uapi --user=username Email add_forwarders \ forwarders='{"info@example.com": ["destination@example.com"]}' ```

Restore email filters:

```bash # Check filters file cat /home/username/etc/example.com/filter.log cat /home/username/etc/emailfilter.log

# List current filters uapi --user=username Email list_filters

# Restore filters from backup # Filters stored in: /home/username/etc/emailfilter

# Rebuild email filter configuration /scripts/rebuildemailfilters username ```

Fix email routing:

```bash # Check current email routing uapi --user=username Email get_routing

# Expected: "local" for accounts hosted on this server # "remote" for external email (Google Workspace, Office 365)

# Set routing to local (if email hosted on server) uapi --user=username Email set_routing routing=local

# Or set to remote (for external email) uapi --user=username Email set_routing routing=remote

# Fix MX records for email routing # WHM > DNS Functions > Edit DNS Zone # MX record should point to mail server ```

### 8. Fix Apache configuration

Rebuild Apache configuration:

```bash # Rebuild vhost configuration for user /scripts/rebuildhttpdconf username

# Or rebuild all vhosts /scripts/rebuildhttpdconf

# Include custom entries if needed /scripts/rebuildhttpdconf --includecustom

# Restart Apache systemctl restart httpd

# Check configuration syntax apachectl configtest # Expected: Syntax OK

# Check for errors systemctl status httpd ```

Fix PHP handler configuration:

```bash # Check PHP version for account # cPanel > Select PHP Version

# Or via command line /usr/local/cpanel/bin/php_interface_list username

# Set PHP version # WHM > MultiPHP Manager > Select domain > Choose PHP version

# Or via command line (EA4) /usr/local/cpanel/bin/set_php_handler --domain example.com --php-version 8.1

# Check .htaccess for PHP directives cat /home/username/public_html/.htaccess | grep -E "php_value|php_flag|AddHandler"

# Common issues after transfer: # - Old PHP version directives # - Removed PHP extensions # - Incompatible ini settings

# Fix .htaccess PHP settings # Edit to match new server's PHP configuration ```

### 9. Verify account functionality

Run cPanel account verification:

```bash # Check account status /scripts/verifyuser username

# Run full cPanel integrity check /scripts/cpcheckuser username

# Check for common issues: # - File ownership # - Permission problems # - Database connectivity # - DNS configuration # - Mail configuration ```

Test website functionality:

```bash # Test website loads curl -I https://example.com

# Expected: HTTP/1.1 200 OK

# Check for errors curl https://example.com 2>&1 | grep -i error

# Test database connection (WordPress) sudo -u username php -r " require '/home/username/public_html/wp-load.php'; global \$wpdb; echo \$wpdb->dbh ? 'Database connected' : 'Database failed'; "

# Test email sending sudo -u username php -r " mail('test@example.com', 'Test Subject', 'Test Body'); echo 'Mail sent'; "

# Check mail logs tail -f /var/log/exim_mainlog | grep example.com ```

### 10. Post-transfer cleanup

Update DNS TTL:

```bash # After successful transfer, reduce TTL for future moves # WHM > DNS Functions > Edit DNS Zone

# Set reasonable TTL values: # - A records: 300-3600 (5 min to 1 hour) # - MX records: 3600 (1 hour) # - TXT/SPF: 3600 (1 hour) # - NS records: 86400 (24 hours)

# Lower TTL before planned migrations # Higher TTL for stable production ```

Clean up old server:

```bash # On OLD server (after confirming successful transfer):

# Terminate account (removes all data) /scripts/terminateaccount username

# Or suspend first (safer) /scripts/suspendacct username "Transferred to new server"

# Wait 7-14 days before full termination # Monitor for any missed data

# Update internal DNS/records # Remove from load balancer # Update monitoring systems ```

Prevention

  • Verify UID consistency across server fleet
  • Run /scripts/fixperms after all transfers
  • Test database connectivity immediately after transfer
  • Verify cron jobs with test executions
  • Monitor DNS propagation with external tools
  • Force AutoSSL immediately after IP change
  • Document all custom configurations before transfer
  • Keep old server running for 7-14 days post-migration
  • **500 Internal Server Error**: Permission or configuration issue
  • **403 Forbidden**: File permissions too restrictive or wrong ownership
  • **Database connection refused**: MySQL grants not restored
  • **Email bouncing**: Forwarders or routing not configured
  • **SSL certificate invalid**: Certificate not reissued for new IP