Cập nhật IP động cho một hoặc nhiều domain sử dụng CloudFlare để truy cập Server tại nhà

Nếu bạn đang tự vận hành Home Server hay NAS tại nhà, nhưng IP public không cố định, việc truy cập từ xa sẽ gặp khó khăn. Giải pháp hiệu quả là dùng Cloudflare để cập nhật IP động cho domain của bạn, tương tự như một dịch vụ DDNS nhưng ổn định, bảo mật, miễn phí và chuyên nghiệp hơn.

1. Yêu cầu hệ thống

  • Một domain được quản lý qua Cloudflare
  • Hệ thống server tại nhà (Ubuntu, Debian, Raspberry Pi, v.v.)
  • Đã mở port truy cập từ xa hoặc cấu hình reverse proxy
  • Công cụ jqcurl cài sẵn

Lưu ý: Tên miền sử dụng phải là loại trả phí (.com, .net, .org, .me, …), không thể sử dụng các loại tên miền miễn phí (.tk, .ga, .ml, .cf, .gq, …) vì Cloudflare không hỗ trợ.

2. Tìm Zone ID và tạo API Token

Xem hướng dẫn cách lấy Zone ID và tạo API Token ở đây

3. Cài đặt Script tự động cập nhật IP

Đầu tiên bạn login SSH vào máy chủ của bạn để thiết lập bảng danh sách và script

3.1 Tạo file chứa danh sách domain

Trong bài này mình hướng dẫn cập nhật 1 hoặc nhiều domain cùng một lúc, vì vậy mình sẽ khởi tạo 1 file TXT chứa danh sách các domain cần cập nhật theo cấu trúc định sẵn như sau
File records.txt mỗi dòng:
zone_id|record_name|api_token|true/false
Đây là file mẫu của mình cho các bạn xem nhé

9a6ef568c2b7b89c251d08574aad4f61|bachtran.net|Nrj9iYBGKDDH1QNeVPGjhmUbooTYgjM-VK53Vo0d|true
87bb8614604e1253a6ea5c57bdc2fb16|domain1.com|mdYNtzb3-QATbaoa18c1BJbRUEYhtf2bqa7etRM4|true
48ba42390521fa214c93fa00eb1d2beb|domain2.com|E1a6R_vWkV6fDo5VnTw0efO2z85_8VKKL1bo6DOt|true

Giải thích:
Zone_ID: lấy trên cloudflare
Record_name: thường là tên domain của bạn
API_token: Tạo API cho điều chỉnh riêng từng domain
True/False: chỉ điền 1 trong 2. Nếu true thì domain của bạn sẽ qua proxy, nếu chọn false thì nó sẽ không qua proxy.

Sử dụng lệnh

nano records.txt

Nên soạn thảo một file ở dưới máy tính của mình để copy dán vào nano cho tiện sau đó lưu lại file.

3.2 Tạo Cloudflare.sh script

Dùng lệnh sau đây để tạo file Cloudflare.sh

nano Cloudflare.sh

Nhập vào nội dung sau đây.

#!/bin/bash

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
RECORDS_FILE="$SCRIPT_DIR/records.txt"

if ! command -v jq &> /dev/null; then
    echo "❌ Lỗi: jq chưa được cài. Vui lòng cài bằng: sudo apt install jq"
    exit 1
fi

if [[ ! -f "$RECORDS_FILE" ]]; then
    echo "❌ File $RECORDS_FILE không tồn tại!"
    exit 1
fi

echo "🚀 Đang lấy địa chỉ IP hiện tại..."
ip=$(curl -s4 https://icanhazip.com/)
if [[ -z "$ip" ]]; then
    >&2 echo "❌ Không thể lấy địa chỉ IP public."
    exit 1
fi
echo "🌐 IP hiện tại: ${ip}"

echo "🔍 Kiểm tra IP cũ của các record..."
all_same_ip=true
while IFS='|' read -r zone_id record_name api_token proxied; do
    response=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records?name=${record_name}&type=A" \
        -H "Authorization: Bearer ${api_token}" \
        -H "Content-Type: application/json")

    old_ip=$(echo "$response" | jq -r '.result[0].content')

    if [[ "$old_ip" != "$ip" ]]; then
        all_same_ip=false
        break
    fi

    sleep 0.2
done < "$RECORDS_FILE"

if $all_same_ip; then
    echo "✅ Tất cả bản ghi đã trùng với IP hiện tại ($ip), không cần cập nhật."
    exit 0
fi

echo "♻️ Phát hiện có sự thay đổi IP. Tiến hành cập nhật..."

total_records=$(wc -l < "$RECORDS_FILE")
record_count=0
updated_count=0
unchanged_count=0
error_count=0
proxy_count=0
no_proxy_count=0
processing_results=()

show_progress() {
    local percentage=$1
    local bar_length=40
    local filled_length=$((percentage * bar_length / 100))
    local empty_length=$((bar_length - filled_length))
    local filled_bar=$(printf "%${filled_length}s" | tr ' ' '=')
    local empty_bar=$(printf "%${empty_length}s" | tr ' ' '-')
    printf "\r➡️ Đang xử lý: [%s%s] %d%%" "$filled_bar" "$empty_bar" "$percentage"
}

while IFS='|' read -r zone_id record_name api_token proxied; do
    ((record_count++))
    percentage=$((record_count * 100 / total_records))
    show_progress "$percentage"

    # Xử lý giá trị proxied (chuyển sang true/false kiểu bool)
    case "${proxied,,}" in
        true)  proxied_bool=true; ((proxy_count++));;
        false) proxied_bool=false; ((no_proxy_count++));;
        *)     proxied_bool=false; ((no_proxy_count++));;
    esac

    result="$record_name - "

    response=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records?name=${record_name}&type=A" \
        -H "Authorization: Bearer ${api_token}" \
        -H "Content-Type: application/json")

    record_id=$(echo "$response" | jq -r '.result[0].id')
    old_ip=$(echo "$response" | jq -r '.result[0].content')

    if [[ "$record_id" == "null" || -z "$record_id" ]]; then
        result+="❌ Không tìm thấy record hoặc Zone ID sai"
        ((error_count++))
    elif [[ "$old_ip" == "$ip" ]]; then
        result+="✅ Không thay đổi"
        ((unchanged_count++))
    else
        update_payload=$(jq -n \
            --arg type "A" \
            --arg name "$record_name" \
            --arg content "$ip" \
            --argjson ttl 120 \
            --argjson proxied "$proxied_bool" \
            '{type: $type, name: $name, content: $content, ttl: $ttl, proxied: $proxied}')

        update_result=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records/${record_id}" \
            -H "Authorization: Bearer ${api_token}" \
            -H "Content-Type: application/json" \
            --data "$update_payload")

        if echo "$update_result" | grep -q '"success":true'; then
            result+="✅ Cập nhật thành công: $old_ip → $ip"
            ((updated_count++))
        else
            result+="❌ Lỗi cập nhật"
            ((error_count++))
        fi
    fi

    processing_results+=("$result")
    sleep 0.2
done < "$RECORDS_FILE"

echo ""

echo -e "\n📋 Kết quả chi tiết:"
for item in "${processing_results[@]}"; do
    echo "  └── $item"
done

echo -e "\n✅ Hoàn tất. Tổng: $total_records | Cập nhật: $updated_count | Không đổi: $unchanged_count | Lỗi: $error_count"
echo -e "🔶 Qua proxy: $proxy_count | Không qua proxy: $no_proxy_count"
echo -e "by bachtran.net"

Sau đó lưu lại. File Cloudflare.shrecords.txt nên ở cùng một thư mục nhé.

Sẵn đây cấp quyền thực thi cho nó luôn

chmod +x cloudflare.sh

Câu lệnh chạy thử nó

./cloudflare.sh

Khi bạn chạy thử thành công nó sẽ hiển thị các thông tin ra như thế này

4. Thiết lập Cron tự động

Để việc này tự động hoàn toàn thì bạn thiết lập cron cho nó tự chạy mỗi 5 phút chẳng hạn

crontab -e

Thêm dòng này vào dưới cùng cron để chạy mỗi 5 phút

*/5 * * * * /bin/bash /home/bachtran/cloudflare.sh

Chú ý thay đổi thành đường dẫn chính xác của riêng bạn /home/bachtran/cloudflare.sh

Chúc thành công !

0 0 đánh giá
Đánh giá bài viết
Theo dõi
Thông báo của
0 Góp ý
Được bỏ phiếu nhiều nhất
Mới nhất Cũ nhất
Phản hồi nội tuyến
Xem tất cả bình luận
0
Rất thích suy nghĩ của bạn, hãy bình luận.x