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:
- Run a cron job that deletes expired records from the database,
- Add expiration logic to your SQL backend so expired records are filtered out,
- 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"
- Before timestamp 1760900000 (BTW that's Oct 19 2025, 18:53:20) it returns IP address
1.2.3.4 - After that timestamp, it returns nothing
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:
1760900000= UNIX expiration timestamp1.2.3.4= desired IPttl= how long resolvers are allowed to cache it for
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
-
It's important to keep
TTLshort, otherwise DNS resolvers might cache your IP address long after it's expired. Unfortunately, you can't setTTLdynamically viaLUArecord, so simply put some low value there. -
You can't change record type (
AtoTXT) dynamically, too. -
Depending on your setup (many users coming and going), you might want to cleanup old records from database from time to time. For me (under 10 users, all of them I know personally) this was not a problem.
If interested, code for Python server implementing this is available at gitlab.