hetzner-dnsapi-proxy proxies DNS API update requests to the Hetzner Cloud API.
Note: Support for the old Hetzner DNS API has been removed since it has been shut down. If upgrading from a setup that used the old DNS API, update your
API_TOKEN(ortokenin the config file) to a Hetzner Cloud API token. ThecloudAPIconfig option andCLOUD_APIenvironment variable are no longer recognized and can be removed from existing configurations.
Get the container image from ghcr.io
| API | Endpoint |
|---|---|
| lego HTTP request | POST /httpreq/presentPOST /httpreq/cleanup(see https://go-acme.github.io/lego/dns/httpreq/) |
| ACMEDNS | POST /acmedns/update(see https://github.com/joohoi/acme-dns#update-endpoint) |
| DirectAdmin Legacy | GET /directadmin/CMD_API_SHOW_DOMAINSGET /directadmin/CMD_API_DNS_CONTROL (only adding A/AAAA/TXT records, everything else always returns 200 OK)GET /directadmin/CMD_API_DOMAIN_POINTER (only a stub, always returns 200 OK)(see https://docs.directadmin.com/developer/api/legacy-api.html and https://www.directadmin.com/features.php?id=504) |
| plain HTTP | GET /plain/update (query params hostname and ip (can be ipv4 for A or ipv6 for AAAA records), if auth method is users then HTTP Basic auth is used) |
| DynDNS2 | GET /nic/update (query params hostname and optional myip (falls back to client IP, ipv4 or ipv6), HTTP Basic auth, responses follow the DynDNS2 token spec) |
Configuration can be passed by environment variables or from a file (with
the -c flag).
Security notes:
- The server speaks plaintext HTTP only. Terminate TLS in front of it (e.g. with a reverse proxy) whenever it is exposed beyond a trusted network - credentials and update values would otherwise travel in clear text.
- The config file holds the Hetzner API token and, optionally, user passwords. Restrict it to the service account (e.g.
chmod 600) and keep it out of version control and container images.
Authorization takes place via a list of domains and ip networks allowed to update them or from a list of users. Both can be provided in a config file while when parsing the configuration from environment variables only the former is supported.
The supported authorization methods are:
allowedDomains: Define ip networks allowed to update specific domains or subdomainsusers: Define users allowed to update specific domains or subdomainsboth: Combination ofallowedDomainsandusers, both must be satisfiedany: Combination ofallowedDomainsandusers, any of the two must be satisfied
To authorize a domain and all of its subdomains, prefix the entry with *.
(for example *.example.com matches example.com's subdomains like
foo.example.com and bar.foo.example.com). A bare example.com entry only
authorizes that exact name - subdomains will be rejected.
Note: The
/nic/updateendpoint follows the DynDNS2 response spec and returns200 OKwith anohosttoken on authorization failure inallowedDomainsmode (a401 badauthis only returned when HTTP Basic auth is actively being used). Thelockoutfeature still applies so repeatednohostresponses from the same client IP eventually trigger a lockout.
Note: Caller-supplied IPs (
myipon/nic/update,ipon/plain/update, JSONvalueon/httpreq/*and/acmedns/update) are taken from the request at face value. They are only as trustworthy as the authenticated client submitting them - there is no server-side verification that the value actually belongs to the caller.
Both features per-client-IP defenses:
rateLimitis a token-bucket throttle applied to every endpoint. Requests aboveburstrefill atrpstokens per second. Excess requests get HTTP 429 (or the DynDNS2abusetoken on/nic/update).lockouttracks consecutive auth failures. AftermaxAttemptsfailures withinwindowSeconds, the client IP is locked out fordurationSeconds. A successful auth clears the counter. Partial failures outside the window are forgotten.
Client IPs are determined after trustedProxies resolution, so requests
traversing a trusted reverse proxy are counted against the real client.
By default all endpoint groups are enabled. You can restrict which groups are active by listing only the ones you want:
plain—/plain/updatenic—/nic/updateacmedns—/acmedns/updatehttpreq—/httpreq/present,/httpreq/cleanupdirectadmin—/directadmin/CMD_API_*
Via config file set the endpoints key; via environment variable set
ENDPOINTS to a comma-separated list (e.g. ENDPOINTS=plain,nic). Listing
any endpoint disables all others not listed.
Every response includes X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Content-Security-Policy: default-src 'none', and
Cache-Control: no-store.
token: verysecrettoken
timeout: 60
auth:
method: both
allowedDomains:
example.com:
- ip: 127.0.0.1
mask:
- 255
- 255
- 255
- 255
users:
- username: user
password: pass
domains:
- example.com
endpoints:
plain: true
nic: true
acmedns: true
httpreq: true
directadmin: true
recordTTL: 60
listenAddr: :8081
trustedProxies:
- 127.0.0.1
rateLimit:
rps: 5
burst: 10
idleSeconds: 600
lockout:
maxAttempts: 10
durationSeconds: 3600
windowSeconds: 900
debug: false| Variable | Type | Description | Required | Default |
|---|---|---|---|---|
API_BASE_URL |
string | Base URL of the API | N | https://api.hetzner.cloud/v1 |
API_TOKEN |
string | Auth token for the API | Y | |
API_TIMEOUT |
int | Timeout for calls to the API in seconds | N | 60 seconds |
RECORD_TTL |
int | TTL that is set when creating/updating records | N | 60 seconds |
ALLOWED_DOMAINS |
string | Combination of domains and CIDRs allowed to update them, example:example1.com,127.0.0.1/32;_acme-challenge.example2.com,127.0.0.1/32 |
Y | |
LISTEN_ADDR |
string | Listen address of hetzner-dnsapi-proxy | N | :8081 |
TRUSTED_PROXIES |
string | Comma-separated list of trusted proxy IPs or CIDR ranges (e.g. 10.0.0.1,192.168.0.0/24). When empty, X-Real-Ip / X-Forwarded-For are ignored. |
N | Trust no proxies |
RATE_LIMIT_RPS |
float | Tokens per second refilled per client IP | N | 5 |
RATE_LIMIT_BURST |
int | Maximum burst size per client IP | N | 10 |
RATE_LIMIT_IDLE_SECONDS |
int | Seconds of inactivity before a client's rate limit bucket is removed | N | 600 |
LOCKOUT_MAX_ATTEMPTS |
int | Failures before lockout | N | 10 |
LOCKOUT_DURATION_SECONDS |
int | Lockout duration in seconds | N | 3600 |
LOCKOUT_WINDOW_SECONDS |
int | Window in seconds during which consecutive failures accumulate | N | 900 |
ENDPOINTS |
string | Comma-separated list of endpoint groups to enable: plain, nic, acmedns, httpreq, directadmin. All enabled when unset. |
N | All enabled |
DEBUG |
bool | Output debug logs of received requests | N | false |