Last active
May 29, 2026 16:23
-
-
Save Septrum101/7467bdf972a22c26af5396b43ec2d767 to your computer and use it in GitHub Desktop.
RouterOS v7 DDNS script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ================================================================ | |
| # Cloudflare DDNS Update Script | |
| # Firmware: RouterOS v7.22+ (also works on v7.23) | |
| # Features: In-memory only | No temp files | Auto fetch zone/record IDs | |
| # Config: Only cfToken and cfDomain required | |
| # ================================================================ | |
| # ============ User Config (modify these) ============= | |
| :global cfDDNSToken "your_cloudflare_api_token"; | |
| :global cfDDNSDomain "ddns.example.com"; | |
| :global cfDDNSZoneLabels 2; | |
| :global cfDDNSInterface "pppoe-out1"; | |
| :global cfDDNSType "A"; | |
| # ---- Cache IDs (Auto-managed) ---- | |
| :global cfDDNSZoneID; | |
| :global cfDDNSRecordID; | |
| # dnsType: "A" for IPv4 (/ip address), "AAAA" for IPv6 (/ipv6 address) | |
| # ======================================================== | |
| # ---- Optional Parameters ---- | |
| :local dnsTTL 1; # TTL in seconds, 1 = auto | |
| :local dnsProxied false; # Enable CF proxy (usually false for DDNS) | |
| :local cfToken $cfDDNSToken; | |
| :local cfDomain $cfDDNSDomain; | |
| :local cfZoneLabels $cfDDNSZoneLabels; | |
| :local cfIntf $cfDDNSInterface; | |
| :local cfType $cfDDNSType; | |
| # ======================================== | |
| # 1. Get local IP from interface | |
| # "A" -> /ip address (IPv4) | |
| # "AAAA" -> /ipv6 address (IPv6) | |
| # ======================================== | |
| :local currentIP ""; | |
| :if ($cfType = "AAAA") do={ | |
| :local addr [/ipv6 address get [find interface=$cfIntf global] address]; | |
| :set currentIP [:pick $addr 0 [:find $addr "/"]]; | |
| } else={ | |
| :local addr [/ip address get [find interface=$cfIntf] address]; | |
| :set currentIP [:pick $addr 0 [:find $addr "/"]]; | |
| } | |
| :if ([:len $currentIP] = 0) do={ | |
| :log warning ("CF-DDNS: Failed to get " . $cfType . " address from interface [" . $cfIntf . "]"); | |
| :return 0; | |
| } | |
| # ======================================== | |
| # 2. Extract zone name from domain | |
| # cfDDNSZoneLabels=2: | |
| # example.com -> example.com | |
| # cfDDNSZoneLabels=3: | |
| # my.work.example.co.uk -> example.co.uk | |
| # ======================================== | |
| :local zoneName $cfDomain; | |
| :local dotCount 0; | |
| :local pos 0; | |
| :while ([:typeof [:find $cfDomain "." $pos]] = "num") do={ | |
| :set pos ([:find $cfDomain "." $pos] + 1); | |
| :set dotCount ($dotCount + 1); | |
| } | |
| :local labelsCount ($dotCount + 1); | |
| :if (($cfZoneLabels > 0) && ($labelsCount > $cfZoneLabels)) do={ | |
| :local needSkip ($labelsCount - $cfZoneLabels); | |
| :local skipped 0; | |
| :local start 0; | |
| :while ($skipped < $needSkip) do={ | |
| :local dotPos [:find $cfDomain "." $start]; | |
| :set start ($dotPos + 1); | |
| :set skipped ($skipped + 1); | |
| } | |
| :set zoneName [:pick $cfDomain $start [:len $cfDomain]]; | |
| } | |
| :log info ("CF-DDNS: Zone name [" . $zoneName . "]"); | |
| # ======================================== | |
| # 3. Fetch Zone ID online (with Cache) | |
| # ======================================== | |
| :local zoneId $cfDDNSZoneID; | |
| :local authHeader ("Authorization: Bearer " . $cfToken); | |
| :if ([:len $zoneId] = 0) do={ | |
| :local zoneUrl ("https://api.cloudflare.com/client/v4/zones?name=" . $zoneName); | |
| :local zoneResp [/tool fetch url=$zoneUrl http-method=get \ | |
| http-header-field=$authHeader as-value output=user]; | |
| :local zoneObj [:deserialize from=json value=($zoneResp->"data")]; | |
| :if (($zoneObj->"success") != true) do={ | |
| :log warning "CF-DDNS: Failed to get zone (check your token)"; | |
| :return 0; | |
| } | |
| :local zones ($zoneObj->"result"); | |
| :if ([:len $zones] = 0) do={ | |
| :log warning ("CF-DDNS: Zone not found [" . $zoneName . "]"); | |
| :return 0; | |
| } | |
| :foreach z in=$zones do={ | |
| :set zoneId ($z->"id"); | |
| :break; | |
| } | |
| :set cfDDNSZoneID $zoneId; | |
| :log info ("CF-DDNS: Fetched and cached Zone ID: " . $zoneId); | |
| } | |
| # ======================================== | |
| # 4. Fetch DNS Record ID online (with Cache) | |
| # ======================================== | |
| :local recordId $cfDDNSRecordID; | |
| :local oldIP ""; | |
| :if ([:len $recordId] = 0) do={ | |
| :local recUrl ("https://api.cloudflare.com/client/v4/zones/" . $zoneId . "/dns_records?name=" . $cfDomain . "&type=" . $cfType); | |
| :local recResp [/tool fetch url=$recUrl http-method=get \ | |
| http-header-field=$authHeader as-value output=user]; | |
| :local recObj [:deserialize from=json value=($recResp->"data")]; | |
| :if (($recObj->"success") != true) do={ | |
| :log warning "CF-DDNS: Failed to get DNS record"; | |
| :return 0; | |
| } | |
| :local records ($recObj->"result"); | |
| :if ([:len $records] = 0) do={ | |
| :log warning ("CF-DDNS: " . $cfType . " record [" . $cfDomain . "] not found, create it manually in Cloudflare first"); | |
| :return 0; | |
| } | |
| :foreach r in=$records do={ | |
| :set recordId ($r->"id"); | |
| :set oldIP ($r->"content"); | |
| :break; | |
| } | |
| :set cfDDNSRecordID $recordId; | |
| :log info ("CF-DDNS: Fetched and cached Record ID: " . $recordId); | |
| } else={ | |
| # If using cached ID, we still need oldIP to check if update is needed | |
| :local recUrl ("https://api.cloudflare.com/client/v4/zones/" . $zoneId . "/dns_records/" . $recordId); | |
| :local recResp [/tool fetch url=$recUrl http-method=get \ | |
| http-header-field=$authHeader as-value output=user]; | |
| :local recObj [:deserialize from=json value=($recResp->"data")]; | |
| :if (($recObj->"success") = true) do={ | |
| :set oldIP ($recObj->"result"->"content"); | |
| } else { | |
| :log warning "CF-DDNS: Cached Record ID failed, clearing cache and retrying next time"; | |
| :set cfDDNSRecordID ""; | |
| :return 0; | |
| } | |
| } | |
| # ======================================== | |
| # 5. Skip if IP unchanged | |
| # ======================================== | |
| :if ($oldIP = $currentIP) do={ | |
| :log info ("CF-DDNS: IP unchanged (" . $currentIP . "), skip update"); | |
| :return 0; | |
| } | |
| # ======================================== | |
| # 6. Update DNS record | |
| # ======================================== | |
| :local updUrl ("https://api.cloudflare.com/client/v4/zones/" . $zoneId . "/dns_records/" . $recordId); | |
| :local proxiedStr (:tostr $dnsProxied); | |
| :local putBody ("{\"type\":\"" . $cfType . "\",\"name\":\"" . $cfDomain . "\",\"content\":\"" . $currentIP . "\",\"ttl\":" . $dnsTTL . ",\"proxied\":" . $proxiedStr . "}"); | |
| :local updResp [/tool fetch url=$updUrl http-method=put \ | |
| http-header-field="Content-Type: application/json, $authHeader" \ | |
| http-data=$putBody \ | |
| as-value output=user]; | |
| :local updObj [:deserialize from=json value=($updResp->"data")]; | |
| :if (($updObj->"success") = true) do={ | |
| :log info ("CF-DDNS: " . $cfDomain . " " . $oldIP . " -> " . $currentIP); | |
| } else={ | |
| :log error "CF-DDNS: Update failed"; | |
| } | |
| # ================================================================ | |
| # Usage | |
| # | |
| # 1. Import script: | |
| # Paste into RouterOS terminal or upload .rsc via WinBox | |
| # | |
| # 2. Set global variables (once): | |
| # /global cfDDNSToken "your_cloudflare_api_token" | |
| # /global cfDDNSDomain "ddns.example.com" | |
| # /global cfDDNSInterface "pppoe-out1" | |
| # /global cfDDNSType "A" | |
| # /global cfDDNSZoneLabels 2 | |
| # | |
| # Note: cfDDNSZoneID and cfDDNSRecordID are managed automatically. | |
| # To force refresh IDs, run: /set cfDDNSZoneID ""; /set cfDDNSRecordID ""; | |
| # | |
| # cfDDNSType: "A" for IPv4, "AAAA" for IPv6 | |
| # | |
| # 3. Add scheduler (every 5 minutes): | |
| # /system scheduler add name="cf-ddns-update" \ | |
| # interval=5m \ | |
| # on-event="/system script run cloudflare-ddns" \ | |
| # policy=read,write,policy,test | |
| # | |
| # 4. Prerequisites: | |
| # - Cloudflare API Token permissions: | |
| # Zone - DNS - Edit | |
| # Zone - Zone - Read | |
| # - Create the A record in Cloudflare dashboard first | |
| # | |
| # 5. To change token/domain/interface/type, just update the global | |
| # variables, no need to restart the scheduler | |
| # ================================================================ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment