DNS API server for self-hosted DynDNS / ACME.
I use CoreDNS to serve my zones, unfortunately it does not support nsupdate protocol. It does auto-reload modified zone files, so an external service can update them.
This project aims to provide DDNS API similar to no-ip.com, so existing ddns-scripts can interact with it.
As a secondary feature it also provides API, which acme-sh can use
to issue TLS certificates using dns-01 challenge.
It also supports LEGO HTTP-Request protocol for the same challenge.
You can use OpenWRT package from my feed: vooon/my-openwrt-feed.
Start server:
zoneomatic --htpasswd ./htpasswd --zone ./example.com.zone --listen 0.0.0.0:9999Update DDNS A record:
curl -u "user:password" \
"http://127.0.0.1:9999/nic/update?hostname=host.example.com&myip=203.0.113.10"Update ACME TXT with acme-dns compatible endpoint:
curl -u "user:password" \
-H "Content-Type: application/json" \
-d '{"subdomain":"host.example.com","txt":"SomeRandomToken"}' \
"http://127.0.0.1:9999/acme/update"- Authentication uses htpasswd entries with bcrypt hashes.
- The server does not terminate TLS by itself; run it behind a reverse proxy with HTTPS.
- If you enable
--accept-proxy, only expose the service behind a trusted proxy/LB.
Usage: zoneomatic --htpasswd=FILE --zone=FILE,... [flags]
DNS Zone file updater
Flags:
-h, --help Show context-sensitive help.
--listen="localhost:9999" Server listen address ($ZM_LISTEN)
--accept-proxy Accept PROXY protocol ($ZM_ACCEPT_PROXY)
--proxy-header-timeout=10s Timeout for PROXY headers ($ZM_PROXY_HEADER_TIMEOUT)
-p, --htpasswd=FILE Passwords file (bcrypt only) ($ZM_HTPASSWD)
-z, --zone=FILE,... Zone files to update ($ZM_ZONE)
--debug Enable debug logging ($ZM_DEBUG)
Note
API description also available in OpenAPI 3 format on /swagger,
e.g. http://localhost:9999/swagger
Return client's IP Address in plain text.
Response status codes:
| Code | Meaning |
|---|---|
| 200 | Success |
| 500 | Unexpected server error |
Update A/AAAA records.
Required HTTP Headers:
| Name | Req | Description |
|---|---|---|
| Authorization | Yes | HTTP Basic Auth |
Query parameters:
| Name | Req | Description |
|---|---|---|
| hostname | Yes | Record name to update |
| myip | No | IP address to set to A/AAAA |
| myipv6 | No | IPv6 address to set to AAAA |
| offline | No | Not supported |
See also: https://www.noip.com/integrate/request
Note
If no myip nor myipv6 provided, a client IP would be used.
Response status codes:
| Code | Meaning |
|---|---|
| 200 | Updated |
| 400 | Bad request (e.g. missing hostname, invalid IP) |
| 401 | Unauthorized |
| 404 | Zone not found |
| 500 | Unexpected server error |
Update ACME DNS TXT records.
Required HTTP Headers:
| Name | Req | Description |
|---|---|---|
| X-Api-User | Yes* | Username from the htpasswd file |
| X-Api-Key | Yes* | Password from the htpasswd file |
| Authorization | Yes* | HTTP Basic Auth, alternative to pair above |
JSON Object fields:
| Name | Req | Description | Example |
|---|---|---|---|
| subdomain | Yes | Record name without _acme-challenge., not a UUID |
foo.example.com |
| txt | Yes | Validation token content for the TXT record | SomeRandomToken |
See also: https://github.com/joohoi/acme-dns
Note
Original ACME-DNS uses X-Api-User/X-Api-Key style authentication and typically a
per-record API key + CNAME alias flow.
This implementation additionally accepts HTTP Basic Auth for simplicity.
Note
For acme.sh option ACMEDNS_BASE_URL should be like that: https://nsapi.example.com/acme,
ACMEDNS_USERNAME & ACMEDNS_PASSWORD - valid user in htpasswd file,
ACMEDNS_SUBDOMAIN - base domain name for which you are requesting certificate.
Auth examples:
Authorization: Basic ... mode:
curl -u "user:password" \
-H "Content-Type: application/json" \
-d '{"subdomain":"foo.example.com","txt":"SomeRandomToken"}' \
"http://127.0.0.1:9999/acme/update"X-Api-User/X-Api-Key mode:
curl \
-H "X-Api-User: user" \
-H "X-Api-Key: password" \
-H "Content-Type: application/json" \
-d '{"subdomain":"foo.example.com","txt":"SomeRandomToken"}' \
"http://127.0.0.1:9999/acme/update"Response status codes:
| Code | Meaning |
|---|---|
| 200 | Updated |
| 400 | Bad request |
| 401 | Unauthorized |
| 404 | Zone not found |
| 500 | Unexpected server error |
Update ACME DNS TXT record, in LEGO HTTP-request format.
Required HTTP Headers:
| Name | Req | Description |
|---|---|---|
| Authorization | Yes | HTTP Basic Auth |
JSON Object fields:
| Name | Req | Description | Example |
|---|---|---|---|
| fqdn | Yes | Record name without _acme-challenge. |
foo.example.com |
| value | Yes | Validation token content for the TXT record | SomeRandomToken |
See also: https://go-acme.github.io/lego/dns/httpreq/
Note
Only HTTPREQ_MODE=default is supported
Response status codes:
| Code | Meaning |
|---|---|
| 200 | Updated |
| 400 | Bad request |
| 401 | Unauthorized |
| 404 | Zone not found |
| 500 | Unexpected server error |
Remove ACME DNS TXT record, in LEGO HTTP-request format.
Required HTTP Headers:
| Name | Req | Description |
|---|---|---|
| Authorization | Yes | HTTP Basic Auth |
JSON Object fields:
| Name | Req | Description | Example |
|---|---|---|---|
| fqdn | Yes | Record name without _acme-challenge. |
foo.example.com |
| value | No | Validation token content for the TXT record, Ignored | SomeRandomToken |
See also: https://go-acme.github.io/lego/dns/httpreq/
Response status codes:
| Code | Meaning |
|---|---|
| 200 | Updated |
| 400 | Bad request |
| 401 | Unauthorized |
| 404 | Zone not found |
| 500 | Unexpected server error |
Custom Zone-o-matic call. Allow to update any existing record(s). Match records by FQDN and type, then each value will be translated to a record.
Required HTTP Headers:
| Name | Req | Description |
|---|---|---|
| Authorization | Yes | HTTP Basic Auth |
JSON Object fields:
| Name | Req | Description | Example |
|---|---|---|---|
| fqdn | Yes | Record domain name. | foo.example.com |
| type | Yes | Record type, case-insensitive. | NS |
| values | Yes | List of records values | ["ns1", "ns2"] |
Note
POST /zm/update updates existing records only. If no matching record exists, it returns an error.
Response status codes:
| Code | Meaning |
|---|---|
| 200 | Updated |
| 400 | Bad request |
| 401 | Unauthorized |
| 404 | Zone not found |
| 500 | Unexpected server error |
Health check endpoint.
Response status codes:
| Code | Meaning |
|---|---|
| 200 | Healthy |
- Multi-part
TXTrecords are kept in parenthesized multiline form. TLSArecords are kept on a single line.