# CloudPanel Domain Management: Domain Migration Script for Nginx & SSL

**URL:** <https://forum.hhf.technology/t/cloudpanel-domain-management-domain-migration-script-for-nginx-ssl/386>\
**Category:** Servers\
**Tags:** cloudpanel\
**Created:** [November 9, 2024, 9:55am UTC](https://forum.hhf.technology/t/cloudpanel-domain-management-domain-migration-script-for-nginx-ssl/386 "2024-11-09T09:55:07Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![hhf.technoloy](https://forum.hhf.technology/user_avatar/forum.hhf.technology/hhf.technoloy/32/68_2.png) [@hhf.technoloy](https://forum.hhf.technology/u/hhf.technoloy)\
**Post date:** [November 9, 2024, 9:55am UTC](https://forum.hhf.technology/t/cloudpanel-domain-management-domain-migration-script-for-nginx-ssl/386/1 "2024-11-09T09:55:07Z")

</div>

## DomainShift CP: CloudPanel Domain Update Automation Tool

1. **Enhanced Error Handling** :

2. **Logging System** :

3. **Backup System** :

4. **Better Organization** :

5. **Security Improvements** :

6. **SQLite Operations** :

7. **Prerequisites Checking** :

To use the script:

1. Save it as `update_domain.sh`

2. Make it executable:

3. Make a Directory name `cloudpanel` in `var/log`  
`mkdir /var/log/cloudpanel`

4. Create a file named `domain_updates.log`  
`touch /var/log/cloudpanel/domain_updates.log`

5. Run it as root:

![update_domain](https://forum-cdn.hhf.technology/original/1X/a789c9d910fee5f1e17d108884b45d2c26a6297d.gif)

 ![image](https://forum-cdn.hhf.technology/original/1X/09127745b78807074e79b1be99ee6e5c29c23327.png)

 ![image](https://forum-cdn.hhf.technology/original/1X/69cd35b85c9f83bf67250215026e34d1c14d0598.png)

The script will:

- Create a backup of all relevant files
- Update all necessary configurations
- Generate new SSL certificates
- Update the CloudPanel database
- Log all actions
- Provide detailed feedback about the process

Logs can be found at `/var/log/cloudpanel/domain_updates.log` for audit purposes.

```bash
#!/bin/bash

# =============================================================================
# DomainShift CP - CloudPanel Domain Migration Tool
# -----------------------------------------------------------------------------
# Purpose: Updates domain names across nginx configuration, SSL certificates,
# and CloudPanel database while maintaining all associated settings.
# Usage: ./update_domain.sh old.domain.com new.domain.com
# Author: hhf
# Last Updated: 2024-11-09
# =============================================================================

set -euo pipefail # Exit on error, undefined vars, and pipe failures

# =============================================================================
# Configuration
# =============================================================================

readonly DB_FILE="/home/clp/htdocs/app/data/db.sq3"
readonly NGINX_CONF_DIR="/etc/nginx/sites-enabled"
readonly NGINX_SSL_DIR="/etc/nginx/ssl-certificates"
readonly NGINX_AUTH_DIR="/etc/nginx/basic-auth"
readonly LOG_FILE="/var/log/cloudpanel/domain_updates.log"
readonly DOMAIN_REGEX="^([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$"

# =============================================================================
# Helper Functions
# =============================================================================

log() {
    local timestamp
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] $1" | tee -a "$LOG_FILE"
}

error_exit() {
    log "ERROR: $1"
    exit 1
}

check_prerequisites() {
    # Check if running as root
    [[$EUID -eq 0]] || error_exit "This script must be run as root"
    
    # Check if required tools are installed
    command -v sqlite3 >/dev/null 2>&1 || error_exit "sqlite3 is required but not installed"
    command -v openssl >/dev/null 2>&1 || error_exit "openssl is required but not installed"
    command -v clpctl >/dev/null 2>&1 || error_exit "clpctl is required but not installed"
    
    # Check if required directories exist
    [[-d "$NGINX_CONF_DIR"]] || error_exit "Nginx config directory not found: $NGINX_CONF_DIR"
    [[-d "$NGINX_SSL_DIR"]] || error_exit "SSL directory not found: $NGINX_SSL_DIR"
    [[-f "$DB_FILE"]] || error_exit "Database file not found: $DB_FILE"
}

validate_domains() {
    local old_domain="$1"
    local new_domain="$2"

    [[-z "$old_domain" || -z "$new_domain"]] && {
        echo "Usage: $0 old.domain.com new.domain.com"
        error_exit "Domain name parameters not set"
    }

    [["$old_domain" =~ $DOMAIN_REGEX]] || error_exit "Invalid old domain format: $old_domain"
    [["$new_domain" =~ $DOMAIN_REGEX]] || error_exit "Invalid new domain format: $new_domain"
}

backup_files() {
    local timestamp
    timestamp=$(date +%Y%m%d_%H%M%S)
    local backup_dir="/root/domain_update_backup_${timestamp}"
    
    mkdir -p "$backup_dir"
    
    # Backup nginx configuration
    cp "$NGINX_CONF_DIR/$OLD_DOMAIN.conf" "$backup_dir/" 2>/dev/null || true
    
    # Backup SSL certificates
    cp "$NGINX_SSL_DIR/$OLD_DOMAIN".* "$backup_dir/" 2>/dev/null || true
    
    # Backup basic auth file
    [[-f "$NGINX_AUTH_DIR/$OLD_DOMAIN"]] && cp "$NGINX_AUTH_DIR/$OLD_DOMAIN" "$backup_dir/" || true
    
    log "Backup created at: $backup_dir"
}

generate_ssl_certificate() {
    local domain="$1"
    
    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
        -subj "/CN=$domain/" \
        -addext "subjectAltName=DNS:$domain" \
        -keyout "$NGINX_SSL_DIR/$domain.key" \
        -out "$NGINX_SSL_DIR/$domain.crt" || error_exit "Failed to generate SSL certificate"
        
    chmod 600 "$NGINX_SSL_DIR/$domain.key"
}

update_nginx_config() {
    local old_domain="$1"
    local new_domain="$2"
    
    # Update nginx configuration content
    sed -i "s/$old_domain/$new_domain/g" "$NGINX_CONF_DIR/$old_domain.conf" || \
        error_exit "Failed to update nginx configuration content"
    
    # Rename nginx configuration file
    mv "$NGINX_CONF_DIR/$old_domain.conf" "$NGINX_CONF_DIR/$new_domain.conf" || \
        error_exit "Failed to rename nginx configuration file"
}

update_database() {
    local old_domain="$1"
    local new_domain="$2"
    local site_id
    
    # Get site ID
    site_id=$(sqlite3 "$DB_FILE" "SELECT id FROM site WHERE domain_name = '$old_domain';") || \
        error_exit "Failed to query site ID"
    
    [[-z "$site_id"]] && error_exit "Domain $old_domain not found in CloudPanel database"
    
    # Update site information
    sqlite3 "$DB_FILE" "BEGIN TRANSACTION;
        UPDATE site 
        SET domain_name = '$new_domain',
            vhost_template = REPLACE(vhost_template, '$old_domain', '$new_domain')
        WHERE id = $site_id;
        
        DELETE FROM certificate 
        WHERE site_id = $site_id 
        AND default_certificate = 1 
        AND type = 1;
        COMMIT;" || error_exit "Failed to update database"
        
    return "$site_id"
}

# =============================================================================
# Main Script
# =============================================================================

main() {
    local OLD_DOMAIN="$1"
    local NEW_DOMAIN="$2"
    
    log "Starting domain update process: $OLD_DOMAIN -> $NEW_DOMAIN"
    
    # Preliminary checks
    check_prerequisites
    validate_domains "$OLD_DOMAIN" "$NEW_DOMAIN"
    
    # Create backup
    backup_files
    
    # Update nginx configuration
    log "Updating nginx configuration..."
    update_nginx_config "$OLD_DOMAIN" "$NEW_DOMAIN"
    
    # Handle SSL certificates
    log "Managing SSL certificates..."
    rm -f "$NGINX_SSL_DIR/$OLD_DOMAIN".{key,crt}
    generate_ssl_certificate "$NEW_DOMAIN"
    
    # Update basic auth if exists
    if [[-f "$NGINX_AUTH_DIR/$OLD_DOMAIN"]]; then
        log "Updating nginx basic auth..."
        mv "$NGINX_AUTH_DIR/$OLD_DOMAIN" "$NGINX_AUTH_DIR/$NEW_DOMAIN" || \
            error_exit "Failed to update basic auth"
    fi
    
    # Update database
    log "Updating CloudPanel database..."
    local site_id
    site_id=$(update_database "$OLD_DOMAIN" "$NEW_DOMAIN")
    
    # Install new certificate
    log "Installing new self-signed certificate..."
    clpctl site:install:certificate \
        --domainName="$NEW_DOMAIN" \
        --privateKey="$NGINX_SSL_DIR/$NEW_DOMAIN.key" \
        --certificate="$NGINX_SSL_DIR/$NEW_DOMAIN.crt" || \
        error_exit "Failed to install new certificate"
    
    # Update certificate settings
    local cert_id
    cert_id=$(sqlite3 "$DB_FILE" "SELECT certificate_id FROM site WHERE id = $site_id")
    sqlite3 "$DB_FILE" "UPDATE certificate SET default_certificate = 1, type = 1 WHERE id = $cert_id" || \
        error_exit "Failed to update certificate settings"
    
    # Restart nginx
    log "Restarting nginx..."
    systemctl restart nginx || error_exit "Failed to restart nginx"
    
    log "Domain update completed successfully"
}

# Execute main function with all arguments
main "$@"

```

---

<div class="post-metadata">

**Author:** ![Ben](https://forum.hhf.technology/user_avatar/forum.hhf.technology/ben/32/3530_2.png) [@Ben](https://forum.hhf.technology/u/Ben)\
**Post date:** [September 26, 2025, 3:26am UTC](https://forum.hhf.technology/t/cloudpanel-domain-management-domain-migration-script-for-nginx-ssl/386/2 "2025-09-26T03:26:28Z")

</div>

Hey, thanks for making this!  
Would it be possible for the script to also update the site user name and root directory?

---

<div class="post-metadata">

**Author:** ![hhf.technoloy](https://forum.hhf.technology/user_avatar/forum.hhf.technology/hhf.technoloy/32/68_2.png) [@hhf.technoloy](https://forum.hhf.technology/u/hhf.technoloy)\
**Post date:** [September 26, 2025, 6:49am UTC](https://forum.hhf.technology/t/cloudpanel-domain-management-domain-migration-script-for-nginx-ssl/386/3 "2025-09-26T06:49:18Z")

</div>

yes, but i cant give you a timeline, V3 is coming i don’t want to work on this and then rework again.
