How to do self-expiring dns records in PowerDNS

Created: — modified: — tags: dns

If you ask your friendly chat bot how to do self-expiring DNS records in PowerDNS Authoritative Server, it will offer you these three options:

  1. Run a cron job that deletes expired records from the database,
  2. Add expiration logic to your SQL backend so expired records are filtered out,
  3. Or use the HTTP API to delete records when they expire.

All perfectly reasonable. All operationally safe1.

But it will happily glance over Lua records, which can easily achieve the same — without cron jobs, cleanup workers, or external state mutation.

And for DynDNS-style setups, Lua is not just viable — it’s clever1.

When this might be important? For example, when you have MX record pointing to your dynamic DNS name (if you host email server at your home, where you have a dynamic "white" IP address) - then it's very important to remove a DNS record for this name as soon as you loose this IP address - otherwise, your email might get delivered to another IP address 🙀

Browsing through PowerDNS Authoritative Server docs, you might notice that it supports LUA record type, which allows scripting during query time and thus are dynamic. Unfortunately, it doesn't hint you about full power of LUA records: that they can use not only PowerDNS-provided functions, but anything from Lua standard library, like... os.time(). For example, look at this zone entry:

example.com. 3 IN LUA A ";if(os.time() < 1760900000) then return '1.2.3.4' end"

That’s it. No deletion. No state transition. Pure logic1.

Morover, Lua records work perfectly with the PowerDNS HTTP API:

curl -X PATCH \
  -H "X-API-Key: supersecret" \
  -H "Content-Type: application/json" \
  http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.com \
  -d '{
    "rrsets": [
      {
        "name": "host.example.com.",
        "type": "LUA",
        "ttl": 30,
        "changetype": "REPLACE",
        "records": [
          {
            "content": "A \";if(os.time() < 1760900000) then return '1.2.3.4' end\""
          }
        ]
      }
    ]
  }'

Here:

And that's all!

When your DynDNS client refreshes, you simply compute new expiration timestamp and replace the Lua record via API. No cleanup necessary!

Caveats to look after

If interested, code for Python server implementing this is available at gitlab.

  1. AI disclaimer: I used ChatGPT to help me write this article. Original ChatGPT-provided text, together with my prompts, is available here ↩ ↩2 ↩3