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:
- IP Detection Service: A custom Golang service at
https://unjx.de/ip - Node-RED Flow: Monitors IP changes and updates DNS
- 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:
currentIpfrom 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:
| Parameter | Value |
|---|---|
| Method | POST |
| URL | https://api.hetzner.cloud/v1/zones/yourdomain.com/rrsets/dyndns/A/actions/set_records |
| Headers | Authorization: Bearer YOUR_API_TOKENContent-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#
- Log into the Hetzner Cloud Console
- Go to “API Tokens” and create a new token
- 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 FlowImport into Node-RED#
- Open Node-RED in your browser
- Hamburger menu → Import
- Select the downloaded
flows.jsonfile - 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_DOMAINwith your actual domain - Adjust record name:
dyndnscan 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=jsonor 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

