Chuyên mục
Network

Cập nhật IP động cho tên miền qua CloudFlare để truy cập homelab tại nhà

Có hai cách để chúng ta có thể truy cập homelab từ bên ngoài:

  • Truy cập trực tiếp bằng Public IP của modem
  • Truy cập qua tên miền được cấu hình trỏ đến Public IP.

Đa số mọi người sẽ chọn cách thứ hai vì nó đơn giản, dễ nhớ hơn so với chuỗi dãy số IP xxx.xxx.xxx.xxx. Tuy nhiên nếu mạng ở nhà sử dụng IP động (đa số trường hợp), tên miền cần phải được tự động cập nhật mỗi khi Public IP thay đổi để tránh kết nối bị lỗi.

Hiện tại mình đang dùng 1 bash script trên Raspberry Pi để tự động cập nhật IP động cho tên miền qua dịch vụ CloudFlare. Nhờ vậy tên miền luôn được trỏ đến IP mới nhất được cấp cho modem mạng ở nhà.

Dưới đây là hướng dẫn chi tiết cách thiết lập

1. Yêu cầu

  • Đã có sẵn tên miền và đã có tài khoản Cloudflare.
  • Tên miền đã được chuyển về CloudFlare quản lý
  • Máy tính / máy ảo chạy Linux (Ubuntu / Arch / CentOS / …) hoặc Raspberry Pi (đời nào cũng được)

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. Cài đặt Cloudflare.sh script

Bạn cần truy cập SSH vào máy tính để thiết lập script.

Tạo file cloudflare.sh trong thư mục $HOME

cd ~
mkdir cloudflare
cd cloudflare
nano cloudflare.sh
Code language: Bash (bash)

Nhập vào nội dung sau

#!/bin/bash

# Forked from benkulbertis/cloudflare-update-record.sh
# CHANGE THESE

# API Token (Recommended)                                                                      #####
auth_token="xxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Domain and DNS record for synchronization
zone_identifier="f1nd7h3fuck1n6z0n31d3n71f13r4l50" # Can be found in the "Overview" tab of your domain
record_name="ipv4.example.org"                     # Which record you want to be synced

# DO NOT CHANGE LINES BELOW

# SCRIPT START
echo -e "Check Initiated"

# Check for current external network IP
ip=$(curl -s4 https://icanhazip.com/)
if [[ ! -z "${ip}" ]]; then
  echo -e "  > Fetched current external network IP: ${ip}"
else
  >&2 echo -e "Network error, cannot fetch external network IP."
fi

# The execution of update
if [[ ! -z "${auth_token}" ]]; then
  header_auth_paramheader=( -H '"Authorization: Bearer '${auth_token}'"' )
else
  header_auth_paramheader=( -H '"X-Auth-Email: '${auth_email}'"' -H '"X-Auth-Key: '${auth_key}'"' )
fi

# Seek for the record
seek_current_dns_value_cmd=( curl -s -X GET '"https://api.cloudflare.com/client/v4/zones/'${zone_identifier}'/dns_records?name='${record_name}'&type=A"' "${header_auth_paramheader[@]}" -H '"Content-Type: application/json"' )
record=`eval ${seek_current_dns_value_cmd[@]}`

# Can't do anything without the record
if [[ -z "${record}" ]]; then
  >&2 echo -e "Network error, cannot fetch DNS record."
  exit 1
elif [[ "${record}" == *'"count":0'* ]]; then
  >&2 echo -e "Record does not exist, perhaps create one first?"
  exit 1
fi

# Set the record identifier from result
record_identifier=`echo "${record}" | sed 's/.*"id":"//;s/".*//'`

# Set existing IP address from the fetched record
old_ip=`echo "${record}" | sed 's/.*"content":"//;s/".*//'`
echo -e "  > Fetched current DNS record value   : ${old_ip}"

# Compare if they're the same
if [ "${ip}" == "${old_ip}" ]; then
  echo -e "Update for A record '${record_name} (${record_identifier})' cancelled.\\n  Reason: IP has not changed."
  exit 0
else
  echo -e "  > Different IP addresses detected, synchronizing..."
fi

# The secret sause for executing the update
json_data_v4="'"'{"id":"'${zone_identifier}'","type":"A","proxied":true,"name":"'${record_name}'","content":"'${ip}'","ttl":120}'"'"
update_cmd=( curl -s -X PUT '"https://api.cloudflare.com/client/v4/zones/'${zone_identifier}'/dns_records/'${record_identifier}'"' "${header_auth_paramheader[@]}" -H '"Content-Type: application/json"' )

# Execution result
update=`eval ${update_cmd[@]} --data $json_data_v4`

# The moment of truth
case "$update" in
*'"success":true'*)
  echo -e "Update for A record '${record_name} (${record_identifier})' succeeded.\\n  - Old value: ${old_ip}\\n  + New value: ${ip}";;
*)
  >&2 echo -e "Update for A record '${record_name} (${record_identifier})' failed.\\nDUMPING RESULTS:\\n${update}"
  exit 1;;
esac
Code language: Bash (bash)

Bạn cần phải điền thông tin vào các dòng sau:

  • auth_token: Cloudflare API Token
  • zone_identifier: Zone ID
  • record_name: Tên miền

3. Tạo Cloudflare API Token

Truy cập vào trang Cloudflare API Token để tạo Token mới

Bấm vào Create Token
Chọn Use Template ở mục Edit Zone DNS
Kéo xuống mục Zone Resources, chọn tên miền bạn muốn sử dụng để cập nhật IP sau đó bấm Continue to Summary
Bấm Create Token
Token đã tạo xong

Bạn cần lưu lại Token vừa mới tạo để lưu vào script đã tạo. Token này sẽ không hiện ra lại trên Cloudflare vì lý do bảo mật. Nếu bạn quên lưu, cần phải xoá Token và tạo lại cái mới.

4. Tìm thông tin Zone ID

Từ trang chủ Cloudflare, bạn bấm vào tên miền muốn sử dụng để cập nhật IP. Sau đó kéo xuống dưới sẽ thấy mục Zone ID. Lưu lại thông số này để điền vào script

Lưu lại thông tin Zone ID

5. Tạo A Record cho tên miền

Ví dụ mình muốn sử dụng tên miền alibaba.thuanbui.me để truy cập đến homelab ở nhà. Bấm vào Add record và tạo một A record mới.

  • Name: điền vào alibaba
  • IPv4: điền tạm 1.1.1.1, thông số này sẽ được tự động cập nhật về IP của modem sau khi chạy script.
Tạo A Record cho subdomain alibaba.thuanbui.me

6. Cập nhật script

Thông tin cần thiết đã có đầy đủ, mình cập nhật lại vào file cloudflare.sh và lưu lại

#!/bin/bash

# Forked from benkulbertis/cloudflare-update-record.sh
# CHANGE THESE

# API Token (Recommended)                                                                  #####
auth_token="LrAB--xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Domain and DNS record for synchronization
zone_identifier="48bxxxxxxxxxxxxxxxxxxxx60" # Can be found in the "Overview" tab of your domain
record_name="alibaba.thuanbui.me"                     # Which record you want to be synced

# DO NOT CHANGE LINES BELOW

# SCRIPT START
echo -e "Check Initiated"

# Check for current external network IP
ip=$(curl -s4 https://icanhazip.com/)
if [[ ! -z "${ip}" ]]; then
  echo -e "  > Fetched current external network IP: ${ip}"
else
  >&2 echo -e "Network error, cannot fetch external network IP."
fi

# The execution of update
if [[ ! -z "${auth_token}" ]]; then
  header_auth_paramheader=( -H '"Authorization: Bearer '${auth_token}'"' )
else
  header_auth_paramheader=( -H '"X-Auth-Email: '${auth_email}'"' -H '"X-Auth-Key: '${auth_key}'"' )
fi

# Seek for the record
seek_current_dns_value_cmd=( curl -s -X GET '"https://api.cloudflare.com/client/v4/zones/'${zone_identifier}'/dns_records?name='${record_name}'&type=A"' "${header_auth_paramheader[@]}" -H '"Content-Type: application/json"' )
record=`eval ${seek_current_dns_value_cmd[@]}`

# Can't do anything without the record
if [[ -z "${record}" ]]; then
  >&2 echo -e "Network error, cannot fetch DNS record."
  exit 1
elif [[ "${record}" == *'"count":0'* ]]; then
  >&2 echo -e "Record does not exist, perhaps create one first?"
  exit 1
fi

# Set the record identifier from result
record_identifier=`echo "${record}" | sed 's/.*"id":"//;s/".*//'`

# Set existing IP address from the fetched record
old_ip=`echo "${record}" | sed 's/.*"content":"//;s/".*//'`
echo -e "  > Fetched current DNS record value   : ${old_ip}"

# Compare if they're the same
if [ "${ip}" == "${old_ip}" ]; then
  echo -e "Update for A record '${record_name} (${record_identifier})' cancelled.\\n  Reason: IP has not changed."
  exit 0
else
  echo -e "  > Different IP addresses detected, synchronizing..."
fi

# The secret sause for executing the update
json_data_v4="'"'{"id":"'${zone_identifier}'","type":"A","proxied":true,"name":"'${record_name}'","content":"'${ip}'","ttl":120}'"'"
update_cmd=( curl -s -X PUT '"https://api.cloudflare.com/client/v4/zones/'${zone_identifier}'/dns_records/'${record_identifier}'"' "${header_auth_paramheader[@]}" -H '"Content-Type: application/json"' )

# Execution result
update=`eval ${update_cmd[@]} --data $json_data_v4`

# The moment of truth
case "$update" in
*'"success":true'*)
  echo -e "Update for A record '${record_name} (${record_identifier})' succeeded.\\n  - Old value: ${old_ip}\\n  + New value: ${ip}";;
*)
  >&2 echo -e "Update for A record '${record_name} (${record_identifier})' failed.\\nDUMPING RESULTS:\\n${update}"
  exit 1;;
esac
Code language: Bash (bash)

Mặc định, script này sẽ kích hoạt chế độ proxy trên Cloudflare nhằm mục đích ẩn IP của modem, giảm nguy cơ bị tấn công vào mạng nhà. Nếu vì lý do nào đó không muốn ẩn IP của mình thì có thể thay đổi thông số "proxied":true thành "proxied":false

7. Chạy thử nghiệm

Thiết lập quyền thực thi cho file cloudflare.sh

chmod +X cloudflare.shCode language: CSS (css)

Chạy thử xem nào

./cloudflare.sh
thuanbui@raspberrypi:~/cloudflare $ ./cloudflare.sh
bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)
Check Initiated
  > Fetched current external network IP: 27.xx.xx.xx
  > Fetched current DNS record value   : 1.1.1.1
  > Different IP addresses detected, synchronizing...
Update for A record 'alibaba.thuanbui.me (48bxxxxxxxxxxxxxxxxxxxx60)' succeeded.
  - Old value: 1.1.1.1
  + New value: 27.xx.xx.xx
Code language: YAML (yaml)

Quay lại Cloudflare, tên miền alibaba.thuanbui.me, mình thấy đã được tự động cập nhật với IP mới. Ngon lành!

8. Thiết lập cron tự động

Việc cuối cùng cần làm là thiết lập cron để script tự động chạy 24/7. Sau đó, không cần bận tâm đến nó nữa.

crontab -eCode language: Nginx (nginx)

Mình chỉnh cho script tự động chạy sau mỗi 10 phút bằng cách thêm dòng này vào dưới cùng

*/10 * * * * /bin/bash /home/thuanbui/cloudflare/cloudflare.shCode language: Markdown (markdown)

Chú ý nhớ thay /home/thuanbui/cloudflare/cloudflare.sh thành đường dẫn đến nơi lưu file cloudflare.sh của bạn.

Chúc bạn thực hiện thành công!

Nếu bài viết của mình mang đến thông tin, kiến thức hữu ích cho bạn, đừng ngại mời mình ly bia để có thêm động lực chia sẻ nhiều hơn nữa. Cám ơn bạn!

Lưu ý: Nếu bạn cần hỗ trợ kỹ thuật, vui lòng gửi câu hỏi trực tiếp ở phần Thảo luận bên dưới, mình sẽ trả lời sớm. Đừng mò vào hỏi trong fanpage Yêu Chạy Bộ, sẽ không có phản hồi đâu!

Bởi Thuận Bùi

Runner at Yêu Chạy Bộ. Blogger at Ba Lô & Dép Lào. Web Developer at TB's Blog.
Follow me: Facebook / Instagram

40 trả lời trong “Cập nhật IP động cho tên miền qua CloudFlare để truy cập homelab tại nhà”

bạn cho hỏi mình bị lỗi nay khi chay ./cloudflare.sh
./cloudflare.sh: command substitution: line 20: syntax error near unexpected token `)’
./cloudflare.sh: command substitution: line 20: `curl -s4 )’
Network error, cannot fetch external network IP.
Network error, cannot fetch DNS record.

Mình mới kiểm tra lại, dòng 20 bị dư 2 dấu < > nên đã cập nhật lại. Bạn chỉnh code lại theo bản mới ở trên nhé. Cám ơn bạn đã thông báo.

em chạy lệnh thì nó không hiện lỗi dòng nào, nhưng nó báo không fetch dns.

root@rpisrv4:/home/pi/cloudflare# bash cloudflare.sh
Check Initiated
> Fetched current external network IP: 113.113.173.201
Network error, cannot fetch DNS record.

thêm nữa, phần Auth_token nó có 2 dấu [–]
nhưng trong phần token em lấy thì nó không có dấu gạch đó. Em phải điền sao cho đúng?

Nhiều khả năng là em nhập sai API Token rồi. Do sai nên nó không truy cập Cloudflare được nên báo lỗi Cannot fetch DNS Record.
Chú ý là cần lấy API Token, không phải Global API nhé.

1. Về API token, em làm theo hướng dẫn của anh để lấy, nhưng API token khi nhập trong code nó định dạng thêm –.
Trong khi API Token của em lấy thì nó không có cái dấu –.
Em phải điền sao cho đúng.
Ví dụ:
API token của em là ABCDEFGHIJKLM.
Khi nhập vào đoạn code của anh sẽ cắt 4 ký tự đầu ra ABCD–EFGH… ==> như vậy có đúng không?

2. Lỗi trả về khi em chạy file bằng 2 lệnh nó khác nhau.
Lệnh sh:
pi@rpisrv4:~/cloudflare $ sh cloudflare.sh
-e Check Initiated
cloudflare.sh: 20: cloudflare.sh: [[: not found
-e Network error, cannot fetch external network IP.
cloudflare.sh: 28: cloudflare.sh: Syntax error: “(” unexpected (expecting “fi”)

Lệnh bash
pi@rpisrv4:~/cloudflare $ bash cloudflare.sh
Check Initiated
> Fetched current external network IP: xxx.113.173.201
Network error, cannot fetch DNS record.

API Token nó hiện ra sao thì paste vô y hệt vậy, không có chia ra gì hết nha em. Thêm — vô là nó sai rồi.
Bản chất cloudflare.sh nó là câu lệnh rồi nên em gõ `sh cloudflare.sh` là sai. Chỉ cần gõ `./cloudflare.sh` là được.

Check Initiated
> Fetched current external network IP: 155.64.16.13
Network error, cannot fetch DNS record.

Admin giúp mình với ạ. Mình đã check đi check lại API token, zone ID đúng nhưng vẫn bị lỗi trên

Báo lỗi Network error, cannot fetch DNS record nghĩa là kết nối đến Cloudflare không thành công. Bạn cần kiểm tra lại các thông số: API Token, Zone ID, record name

Trang API của Cloudflare không bị gì cả nhé. Bạn có thể test curl theo lệnh này: curl -X GET "https://api.cloudflare.com/client/v4/zones/Thay-ZONE-ID-vô-đây" -H "Content-Type:application/json" -H "Authorization: Bearer Thay-API-Token-vô-đây". Lưu ý là gõ lệnh vào Terminal, không phải gõ vào trình duyệt. Nếu thông tin chính xác nó sẽ trả về kết quả tương tự như sau

{"result":{"id":"aeb40eaxxxxxxf51a0b","name":"thuanbui.me","status":"active","paused":false,"type":"full","development_mode":0,"name_servers":["ian.ns.cloudflare.com","melinda.ns.cloudflare.com"],"original_name_servers":["dns1.registrar-servers.com","dns2.registrar-servers.com","dns3.registrar-servers.com","dns4.registrar-servers.com","dns5.registrar-servers.com"],"original_registrar":"namesilo, llc r203-me","original_dnshost":null,"modified_on":"2020-04-24T22:27:18.820906Z","created_on":"2016-03-12T12:06:29.074831Z","activated_on":"2016-03-12T12:08:53.006040Z","meta":{"step":4,"custom_certificate_quota":0,"page_rule_quota":3,"phishing_detected":false,"multiple_railguns_allowed":false},"owner":{"id":"d139f5e53xxxxxxc0d8","type":"user","email":"xxxxx@gmail.com"},"account":{"id":"b9xxxxxx9d4bb","name":"xxxxxx7@gmail.com"},"permissions":["#access:edit","#access:read","#analytics:read","#app:edit","#auditlogs:read","#billing:edit","#billing:read","#cache_purge:edit","#dns_records:edit","#dns_records:read","#healthchecks:edit","#healthchecks:read","#image:edit","#image:read","#lb:edit","#lb:read","#legal:edit","#legal:read","#logs:edit","#logs:read","#member:edit","#member:read","#organization:edit","#organization:read","#ssl:edit","#ssl:read","#stream:edit","#stream:read","#subscription:edit","#subscription:read","#teams:edit","#teams:pii","#teams:read","#teams:report","#waf:edit","#waf:read","#waitingroom:edit","#waitingroom:read","#webhooks:edit","#webhooks:read","#worker:edit","#worker:read","#zaraz:edit","#zaraz:read","#zone:edit","#zone:read","#zone_settings:edit","#zone_settings:read"],"plan":{"id":"0feeeeeeeexxxxxxe","name":"Free Website","price":0,"currency":"USD","frequency":"","is_subscribed":false,"can_subscribe":false,"legacy_id":"free","legacy_discount":false,"externally_managed":false}},"success":true,"errors":[],"messages":[]}

Em chạy lệnh “curl” trên thì ra đúng kết quả như anh nói rồi ạ, nhưng chạy /.cloudflare.sh thì vẫn bị lỗi “Network error, cannot fetch DNS record”

Anh giúp em với ạ

Nếu chạy curl ra đúng thì có nghĩa các thông số đều đúng. Chạy file sh báo lỗi thì nhiều khả năng là phần thiết lập thông số trong file cloudflare.sh bị sai chỗ nào rồi. Kiểm tra lại 5 thông số này:

  • auth_email: điền email tài khoản Cloudflare
  • auth_key: bỏ trống
  • auth_toke: điền vào Token Key
  • zone_identifier: mã Zone
  • record_name: tên miền sub domain

Check Initiated
> Fetched current external network IP: 115.77.50.116
Network error, cannot fetch DNS record.

Mình đã check đi check lại API token, zone ID đúng nhưng vẫn bị lỗi trên.
Mình đã chạy curl ở bình luận trên, thông tin token , zone id mình củng đều đúng.
Mình đã thêm auth_email luôn rồi.
Mong admin giúp đỡ

Bạn cập nhật lại script trong bài nhé. Mình mới cập nhật lại script mới, cái cũ bị lỗi dư kí tự < và > nên fetch DNS bị lỗi. Sorry bạn nhiều.

a cho e hỏi, nếu có nhiều script để chạy cho nhiều subdomain thì setup sao vậy a? e có thử thêm nhiều dòng script vào cron như vầy:
*/10 * * * * /bin/bash /home/thuanbui/cloudflare/1.sh
*/10 * * * * /bin/bash /home/thuanbui/cloudflare/2.sh
*/10 * * * * /bin/bash /home/thuanbui/cloudflare/3.sh
chỉ có cái 1.sh là chạy auto được. ( 1,2,3.sh khi chạy tay đều ok) cám ơn a.

Bạn thử kết hợp tất cả thành 1 dòng như thế này xem có chạy ổn không nhé: */10 * * * * /bin/bash /home/thuanbui/cloudflare/1.sh ; /bin/bash /home/thuanbui/cloudflare/2.sh ; /bin/bash /home/thuanbui/cloudflare/3.sh

Bác ơi xem giúp e xem tại sao sau khi chạy script nó lại báo như thế này. thanks bác, mọi thông số e đã nhập đúng.
Check Initiated
> Fetched current external network IP: 113.xxx.xxx.94
> Fetched current DNS record value : 8.8.8.8
> Different IP addresses detected, synchronizing…
Update for A record ‘home.vinhvinhcloud.tk (f45313b2974xxxxxxx646fb3c94386a6)’ failed.
DUMPING RESULTS:

E có di chuyển bằng lệnh cd như hd bác rồi ạ. Nhưng nó vẫn báo như kia 🙁

lethanhtung@ubuntu:~$ cd cloudflare
lethanhtung@ubuntu:~/cloudflare$ dir
cloudflare.sh
lethanhtung@ubuntu:~/cloudflare$ sudo chmod +X cloudflare.sh
[sudo] password for lethanhtung:
lethanhtung@ubuntu:~/cloudflare$ sudo ./cloudflare.sh
sudo: ./cloudflare.sh: command not found
lethanhtung@ubuntu:~/cloudflare$

Khi em chạy nó hiện ra lỗi này ạ:
root@HungNAS:~# chmod +X cloudflare.sh
root@HungNAS:~# chmod +X cloudflare.sh
root@HungNAS:~# ./cloudflare.sh
-bash: ./cloudflare.sh: /bin/bash^M: bad interpreter: No such file or directory

Mình cũng bị lỗi Network error, cannot fetch DNS record. như mấy bạn trên, tất cả mọi thứ mình đều điền đúng và kiểm tra rất nhiều lần, không biết là bị sao ạ? Mình dùng debian 11

bài viết quá hay cảm ơn bác ạ, hôm qua em nghe nói là có ông nào đó dùng duckdns xong ném cái địa chỉ của nó vào trong value cname mà k rõ lắm bác có thể giải thích cho em không ạ

Cái địa chỉ của DuckDNS nó đã được trỏ về đúng IP ở nhà. Khi bạn set CNAME là địa chỉ DuckDNS thì cái tên miền sẽ được trỏ về cái IP của DuckDNS.

Trả lời bvn90 Hủy

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *