Synology + Cloudflare DNS - Dynamic IP Updates Without Tears (or Cronjobs)

If you run a Synology NAS at home and expose services through your own domain, you’ve probably met the classic villain: your ISP changes your public IP. One moment your domain points to your NAS, the next moment it points to… yesterday.

In this setup we solved that by building a tiny Cloudflare Worker that acts as a “DDNS gateway”. Synology DSM can only call a simple GET URL for DDNS updates, but Cloudflare’s DNS API wants authenticated requests. So the Worker translates DSM’s “DDNS ping” into a proper Cloudflare DNS update.

No cronjobs. No third-party DDNS services. And no more wondering why your NAS suddenly moved to a different IP overnight.

Prerequisites

Step 1: Generate a DDNS secret token

This token is not a Cloudflare API token.
It is a private shared secret between DSM and the Worker.

openssl rand -hex 32

Save the output somewhere safe (a password manager is a very good idea).
We will refer to this value as:

Step 2: Create a Cloudflare API token for DNS updates

In the Cloudflare dashboard:

  1. Go to My Profile → API Tokens → Create Token
  2. Create a custom token with:
    • Permissions: Zone → DNS → Edit
    • Zone Resources: Include → Specific zone → your domain
  3. Create the token and copy it

We will refer to this value as:

This token will live only inside the Worker, never on the NAS.

Step 3: Retrieve the Zone ID

You can copy the Zone ID from the Cloudflare domain overview, or fetch it via the API.

First, store your Cloudflare API token locally:

read -s CF_API_TOKEN

Paste the token and press Enter.

Now query the API:

curl -s \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/zones?name=somedomain.xy" \
| python3 -m json.tool

Look for:

result[0].id

That value is your:

Step 4: Retrieve the DNS Record ID

For the root record (@), Cloudflare’s API uses the full domain name.

curl -s \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records?type=A&name=somedomain.xy" \
| python3 -m json.tool

Replace ZONE_ID with the real value from the previous step.

From the response, copy:

result[0].id

That value is your:

Also note:

Step 5: Create the Cloudflare Worker

In Cloudflare:

Paste the following Worker code:

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    // Expected query parameters:
    // /update?token=...&ip=...
    const reqToken = url.searchParams.get("token");
    const ip = url.searchParams.get("ip");

    // Authenticate DSM
    if (!reqToken || reqToken !== env.DDNS_TOKEN) {
      return new Response("unauthorized", { status: 401 });
    }

    if (!ip) {
      return new Response("missing ip", { status: 400 });
    }

    // Update DNS record via Cloudflare API
    const res = await fetch(
      `https://api.cloudflare.com/client/v4/zones/${env.ZONE_ID}/dns_records/${env.RECORD_ID}`,
      {
        method: "PUT",
        headers: {
          Authorization: `Bearer ${env.API_TOKEN}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          type: "A",
          name: env.HOSTNAME,
          content: ip,
          ttl: 120,
          proxied: false,
        }),
      }
    );

    const data = await res.json();

    if (!data.success) {
      return new Response(JSON.stringify(data), { status: 500 });
    }

    return new Response("ok", { status: 200 });
  },
};

Deploy the Worker.

Step 6: Configure Worker environment variables

Open the Worker settings and add the following encrypted variables:

Save and deploy.

Step 7: Test the Worker manually

Your Worker will be reachable at a URL similar to:

https://your-cloudflare-worker-name.mika-stumborg.workers.dev

Test it with a known IP:

curl -i \
"https://stumborg-server-ip-update.mika-stumborg.workers.dev/update?token=YOUR_DDNS_TOKEN&ip=1.2.3.4"

Expected result:

Verify DNS:

dig @1.1.1.1 stumborg.de A +short

Expected output:

1.2.3.4 <- YOUR IP

If you still see the old IP for a moment, congratulations — you just met DNS caching.

Step 8: Configure DSM (Custom DDNS provider)

DSM path:
Control Panel → External Access → DDNS

Step 8.1: Create a custom provider

Click Customize provider and add a new one.

Query URL:

https://stumborg-server-ip-update.mika-stumborg.workers.dev/update?token=__PASSWORD__&ip=__MYIP__

Notes:

Save the provider.

Step 8.2: Create the DDNS entry

Add a new DDNS entry with:

Click Test Connection.

Expected result:

Step 9: A note on renaming Workers

If you use the workers.dev URL directly and rename the Worker, the URL changes.

DSM will keep calling the old URL and DDNS updates will fail silently.

Fix:

Recommended long-term solution:

https://ddns.somedomain.xy/update

This way DSM never needs to change, even if the Worker is renamed or replaced.

Troubleshooting

DSM test fails

Worker returns 401 unauthorized

Worker returns 500 with authentication errors

Conclusion:

You now have:

No cronjobs, no third-party DDNS services, and no late-night debugging sessions wondering why your NAS disappeared.

From here, you can easily extend this setup with IPv6 (AAAA records), multiple hostnames, or automatic IP detection inside the Worker using request headers.