Skip to main content
Building Your Own DynDNS Service with Hetzner DNS API and Node-RED

Building Your Own DynDNS Service with Hetzner DNS API and Node-RED

Table of Contents

Dynamic DNS (DynDNS) is an essential service for anyone running servers from home or needing remote access to their systems. Instead of relying on external DynDNS providers, I’ll show you today how to build your own DynDNS service using the Hetzner DNS API and Node-RED.

Why Build Your Own DynDNS?
#

Most internet providers assign dynamic IP addresses that change regularly. Commercial DynDNS services often cost money or have limitations. With your own solution, you get:

  • Full control over your DNS entries
  • No monthly costs (only the domain at Hetzner)
  • Unlimited subdomains without extra charges
  • Integration into existing automations

Prerequisites
#

For this setup, you’ll need:

  • A domain managed by Hetzner DNS
  • Node-RED installation
  • Hetzner DNS API Token

The Architecture
#

Our DynDNS system consists of several components:

  1. IP Detection Service: A custom Golang service at https://unjx.de/ip
  2. Node-RED Flow: Monitors IP changes and updates DNS
  3. Hetzner DNS API: Automatically updates DNS entries

IP Detection Service in Golang
#

First, I developed a simple Golang service that returns the public IP address of the requesting client. The service runs at https://unjx.de/ip and returns JSON:

{
  "ip": "203.0.113.1"
}

The advantage over services like ipify.org: You have full control and can add additional information if needed. Feel free to use ipify.org or similar services instead. Otherwise, here’s the exemplary code for your own IP service:

package main

import (
    "encoding/json"
    "net"
    "net/http"
    "strings"
)

type IPResponse struct {
    IP string `json:"ip"`
}

func getClientIP(r *http.Request) string {
    // Check X-Forwarded-For header
    xff := r.Header.Get("X-Forwarded-For")
    if xff != "" {
        ips := strings.Split(xff, ",")
        return strings.TrimSpace(ips[0])
    }

    // Check X-Real-IP header
    if ip := r.Header.Get("X-Real-IP"); ip != "" {
        return ip
    }

    // Fall back to RemoteAddr
    ip, _, _ := net.SplitHostPort(r.RemoteAddr)
    return ip
}

func ipHandler(w http.ResponseWriter, r *http.Request) {
    ip := getClientIP(r)

    response := IPResponse{
        IP: ip,
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(response)
}

func main() {
    http.HandleFunc("/ip", ipHandler)
    http.ListenAndServe(":8080", nil)
}

Node-RED Flow Structure
#

The Node-RED flow is the heart of our DynDNS system. It performs the following steps:

1. Regular IP Polling
#

// Runs every minute
// HTTP Request to https://unjx.de/ip

2. IP Comparison
#

The flow compares the new IP with the stored IP:

  • Read global variable: currentIp from flow storage
  • Extract new IP: From the API response from unjx.de/ip
  • Perform comparison: Only proceed on change
  • Prepare payload: Hetzner API format with new IP address

Function Logic (Function Node):

const currentIp = global.get('currentIp');
const newIp = msg.payload.ip;

if (currentIp !== newIp) {
  msg.ip = newIp;
  msg.payload = {
    records: [{ value: newIp }],
  };
  return msg;
}

3. DNS Update via Hetzner API
#

HTTP Request Configuration:

ParameterValue
MethodPOST
URLhttps://api.hetzner.cloud/v1/zones/yourdomain.com/rrsets/dyndns/A/actions/set_records
HeadersAuthorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
Payload{"records": [{"value": "203.0.113.1"}]}

API Endpoint Details:

  • Zone: Your domain (e.g., example.com)
  • Record Name: dyndns (customizable)
  • Record Type: A (IPv4 address)
  • Action: set_records (overwrites existing entries)

4. Status Monitoring
#

The flow monitors the status of DNS changes in multiple steps:

Step 1: Initiate DNS update

  • HTTP 201: DNS change successfully started
  • Other codes: Error in API call

Step 2: Wait 10 seconds (Delay Node)

  • Hetzner API needs time for processing

Step 3: Query status

  • URL: https://api.hetzner.cloud/v1/actions?id={action_id}
  • HTTP 200: Status query successful

Step 4: Check result

  • “success”: DNS update completed → Save IP
  • “running”: Still processing → Debug output
  • “error”: Update failed → Error handling

Hetzner DNS API Setup
#

Create API Token
#

  1. Log into the Hetzner Cloud Console
  2. Go to “API Tokens” and create a new token
  3. Give it read and write permissions for DNS

Configure DNS Zone
#

Create an A record for your DynDNS entry:

  • Name: dyndns (or any name)
  • Type: A
  • Value: Any IP (will be automatically overwritten)
  • TTL: 60 (for quick updates)

Node-RED Configuration
#

Configure HTTP Request Nodes
#

For all Hetzner API calls, you must set the following headers:

  • Authorization: Bearer YOUR_API_TOKEN
  • Content-Type: application/json

Global Variables
#

The flow uses a global variable currentIp to store the current IP:

// Setting the IP
global.set('currentIp', msg.ip);

// Reading the IP
const currentIp = global.get('currentIp');

Advanced Features
#

Error Handling
#

The flow includes comprehensive error handling:

  • HTTP status code checking
  • Retry mechanism for API errors
  • Debug outputs for error diagnosis

Customizable Intervals
#

By default, the flow checks the IP every minute. For less critical applications, you can increase the interval to 5 or 15 minutes.

Security Aspects
#

API Token Protection
#

  • Use separate API tokens only for DNS operations
  • Limit token permissions to the minimum
  • Rotate tokens regularly

Download and Installation
#

You can download the complete Node-RED flow here:

Download Node-RED Flow

Import into Node-RED
#

  1. Open Node-RED in your browser
  2. Hamburger menu → Import
  3. Select the downloaded flows.json file
  4. Adapt the configuration to your domain

Required Adjustments
#

After import, you need to adjust the following values:

In the “Set Record” node:

  • Adjust URL: https://api.hetzner.cloud/v1/zones/YOUR_DOMAIN/rrsets/dyndns/A/actions/set_records
  • Replace domain: YOUR_DOMAIN with your actual domain
  • Adjust record name: dyndns can be chosen arbitrarily

In all Hetzner API nodes:

  • Authorization header: Bearer YOUR_API_TOKEN
  • Insert API token from Hetzner Cloud Console

Adjust IP service (optional):

  • Default: https://unjx.de/ip (my service)
  • Alternative: https://api.ipify.org?format=json or others

Troubleshooting
#

Common Problems
#

IP is not being updated:

  • Check API token permissions
  • Verify DNS zone configuration
  • Look at Node-RED debug output

Too frequent updates:

  • Increase check interval
  • Implement an IP change threshold

API rate limits:

  • Reduce query frequency
  • Implement exponential backoff

Debug Tips
#

Enable debug nodes for the following points:

  • IP query responses
  • Hetzner API responses
  • Error messages

Conclusion
#

With this setup, you have a full-featured DynDNS service that:

  • Reliably detects your IP changes
  • Automatically performs DNS updates
  • Transparently informs about all changes
  • Is extensible for additional features

The combination of Hetzner DNS API and Node-RED offers maximum flexibility at low cost. Your own IP service gives you additional control and independence.

Extension ideas:

  • Multiple DNS records for different services
  • Webhook integration for other automations
  • Monitoring and alerting for failures
  • Backup DNS provider for redundancy
Florian Hoss
Author
Florian Hoss