diff --git a/docs/servers.md b/docs/servers.md
index 8c97d456bb119d866e599095c3f205ea611bf012..f72f11d190e61c8c1af21af776391a6101412748 100644
--- a/docs/servers.md
+++ b/docs/servers.md
@@ -85,7 +85,6 @@ So to set up this server from scratch, run:
 
 ### Services
   - Regular mirror.
-  - Running a authoritative DNS server (PowerDNS) for our GeoIP mirror
 
 ## reproducible.archlinux.org
 
@@ -135,6 +134,7 @@ Prometheus, and Grafana server which receives selected performance/metrics from
 
 ### Services
   - Redirects (nginx redirects)
+  - Authoritative DNS server (PowerDNS) for ACME DNS challenges
   - ping
 
 ## security.archlinux.org
diff --git a/group_vars/all/geo.yml b/group_vars/all/geo.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a70e3a1585d452338535079b5f80eb1f20a76ff2
--- /dev/null
+++ b/group_vars/all/geo.yml
@@ -0,0 +1,5 @@
+geo_acme_dns_challenge_ns: redirect.archlinux.org
+geo_domains:
+  - geo.mirror.pkgbuild.com
+geo_health_check_paths:
+  geo.mirror.pkgbuild.com: /lastupdate
diff --git a/group_vars/geo_mirrors.yml b/group_vars/geo_mirrors.yml
deleted file mode 100644
index f1babee47d019f34d554d97653ec80ebdc1d5677..0000000000000000000000000000000000000000
--- a/group_vars/geo_mirrors.yml
+++ /dev/null
@@ -1,2 +0,0 @@
----
-certbot_dns_support: true
diff --git a/group_vars/geo_mirrors/misc.yml b/group_vars/geo_mirrors/misc.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e560d4eb51c572d53a8835f02774a3a42958a954
--- /dev/null
+++ b/group_vars/geo_mirrors/misc.yml
@@ -0,0 +1,3 @@
+---
+certbot_dns_support: true
+geo_mirror_domain: "geo.mirror.pkgbuild.com"
diff --git a/group_vars/geo_mirrors/vault_certbot.yml b/group_vars/geo_mirrors/vault_certbot.yml
new file mode 120000
index 0000000000000000000000000000000000000000..760d5f94f435cbe9cc884917061dc28dbcb1445b
--- /dev/null
+++ b/group_vars/geo_mirrors/vault_certbot.yml
@@ -0,0 +1 @@
+../../host_vars/redirect.archlinux.org/vault_certbot.yml
\ No newline at end of file
diff --git a/group_vars/mirrors/misc.yml b/group_vars/mirrors/misc.yml
index 10bf4901525592e4a86268de31b084c3ce35326d..a424fa109db74921a3edd248d707887f9d081e3d 100644
--- a/group_vars/mirrors/misc.yml
+++ b/group_vars/mirrors/misc.yml
@@ -1,6 +1,5 @@
 ---
 archweb_db_host: "{{ hostvars['archlinux.org']['wireguard_address'] }}"
-geo_mirror_domain: "geo.mirror.pkgbuild.com"
 
 # raise tcp window limits to 32MiB
 tcp_rmem: "10240 87380 33554432"
diff --git a/group_vars/mirrors/vault_certbot.yml b/group_vars/mirrors/vault_certbot.yml
deleted file mode 100644
index 9a7c83b1e5f8c02ad4226b33ab0449c2cb1437ea..0000000000000000000000000000000000000000
--- a/group_vars/mirrors/vault_certbot.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-$ANSIBLE_VAULT;1.1;AES256
-33353333666434623961613663633633383731373033316562663738626365613338376533353063
-6630303162373830353863393932363365666130346235340a653238636534636266633137313435
-38386562313930373762386635346264363839623239616662663733636262326331656365643732
-3861396531336463320a623339333461316132666333326136326561633966636136346636303662
-31353932303931666361333038356363633234343130633831636632383063313135616633343263
-38383562326464363061633031636263313534363035656230323137303663653966346231336535
-36653835626632393232633538616365383532643830636633666635393335336538356631353039
-66363836653935316664353161363038376562333764613062316536643034623436303337396639
-64316234623830613739303866653139316362663461376132616464613432303734373761373434
-65323965623431376665353338316531346363303338613863633030656136643933363331396539
-66663362346530643332386436653663336564623664303838386637353061376561626364383433
-37616133643861646536363535613133366664643764356665343162623439333462323634386134
-66343335656334356466636430613634393235613462666362656632316665663235346233363435
-64343031376230393735333761376561393838633734646434626333306666373231353461343561
-38666266666230383330306566653438633566613565386565383565356532653438376234356233
-62373434656634343061333535663135396432383039306566626636666163356534306665623765
-33363030356637376462323934313731326236623765613666356165336165313366
diff --git a/host_vars/mirror.pkgbuild.com/misc b/host_vars/mirror.pkgbuild.com/misc
index 8759253d8d08ac13fe643bead1f1bdea8052e143..1663410dd031d63e5835868ca0f72dbc108bc897 100644
--- a/host_vars/mirror.pkgbuild.com/misc
+++ b/host_vars/mirror.pkgbuild.com/misc
@@ -1,7 +1,6 @@
 ---
 mirror_domain: mirror.pkgbuild.com
 mirror_debug_packages: false
-geomirror_acme_challenge: true
 archweb_mirrorcheck_locations: [20, 21]
 filesystem: btrfs
 
diff --git a/host_vars/redirect.archlinux.org/misc b/host_vars/redirect.archlinux.org/misc
index 8a6ff0110f457e0236813ad1b51f935a466b6fa3..ce24a877c423ca713852a3e505bbc6615e547cdc 100644
--- a/host_vars/redirect.archlinux.org/misc
+++ b/host_vars/redirect.archlinux.org/misc
@@ -2,3 +2,6 @@
 filesystem: btrfs
 wireguard_address: 10.0.0.25
 wireguard_public_key: n11Ps2sc0Cxsi1sLaYFq7dkhlDtTnOZCGovRYbzDGR8=
+
+ipv4_address: "95.216.195.133"
+ipv6_address: "2a01:4f9:c010:2636::1"
diff --git a/host_vars/redirect.archlinux.org/vault_certbot.yml b/host_vars/redirect.archlinux.org/vault_certbot.yml
new file mode 100644
index 0000000000000000000000000000000000000000..224f5bbcdba4860643ce2a9b8f0ee6d27833e3ac
--- /dev/null
+++ b/host_vars/redirect.archlinux.org/vault_certbot.yml
@@ -0,0 +1,18 @@
+$ANSIBLE_VAULT;1.1;AES256
+39626637376631343762626362663831313061353261646164316339663936363938396561363864
+3761623339613362373235326161303736303634333564350a393861623461316661646239393935
+64333234383435313865653463616139393562633735616331343964623032326534393138616161
+6462616265666633380a393862646464373438633835383239623435373636613964623839663939
+39373638356461383331393732626665373436653137373666303465666632383133333237386564
+61353965333432323432383365313263336234366163363330663234656530326265373530663238
+37353561663035363239653763383731313062646538383839383831306562336335363236373036
+33613562623661343965626164386332306164373861316561383239666261393464656536373062
+35646637303036333138643966383239666564323539653866373738346565346238323266376434
+39383064343164373537353866363834663066363333343035373832653261353966653662333736
+32626662636330313261643636663233353536396136353263666461616630393164316435613264
+64643563333337396439643036623739303766313661316266343962386630316366346432376537
+66333863343362323362356333613064613333653161663564616234363263373863663530353038
+37316661376435373239643035343664653133363862323536613164386136376164663763316362
+63646363326333663637613761373032383135393331663361363462386631653266336532663938
+36363135316634383062613562306332663363383630323762333334346339346161393536353466
+30656162633164376635313839646633663133343736386630383439666636613963
diff --git a/playbooks/mirrors.yml b/playbooks/mirrors.yml
index 75184d5663166501f4c0581989ff8ab3af302852..5e562e6add24e788c39e6ef842e4a87e2015a55f 100644
--- a/playbooks/mirrors.yml
+++ b/playbooks/mirrors.yml
@@ -15,4 +15,4 @@
     - { role: promtail }
     - { role: fail2ban }
     - { role: wireguard }
-    - { role: geomirror, when: "inventory_hostname == 'mirror.pkgbuild.com' or 'geo_mirrors' in group_names" }
+    - { role: geo_dns, when: "'geo_mirrors' in group_names" }
diff --git a/playbooks/redirect.archlinux.org.yml b/playbooks/redirect.archlinux.org.yml
index f2d15df759f53c65a5674998d05a6effbef97b95..53fbc6ba2c14ced53970fccbb860d5e2db750ae6 100644
--- a/playbooks/redirect.archlinux.org.yml
+++ b/playbooks/redirect.archlinux.org.yml
@@ -14,3 +14,4 @@
     - { role: promtail }
     - { role: hardening }
     - { role: ping }
+    - { role: acme_dns_challenge }
diff --git a/roles/geomirror/handlers/main.yml b/roles/acme_dns_challenge/handlers/main.yml
similarity index 100%
rename from roles/geomirror/handlers/main.yml
rename to roles/acme_dns_challenge/handlers/main.yml
diff --git a/roles/geomirror/tasks/main.yml b/roles/acme_dns_challenge/tasks/main.yml
similarity index 68%
rename from roles/geomirror/tasks/main.yml
rename to roles/acme_dns_challenge/tasks/main.yml
index 55c15d03ab13cb32cd6309f8934f24c67ebbefdd..77e21e66f38235b8b83515860ae507ac01687c18 100644
--- a/roles/geomirror/tasks/main.yml
+++ b/roles/acme_dns_challenge/tasks/main.yml
@@ -1,41 +1,36 @@
 ---
-- name: install powerdns and geoip
-  pacman: name=powerdns,libmaxminddb,geoip,yaml-cpp state=present
+- name: install powerdns
+  pacman: name=powerdns state=present
 
 - name: install PowerDNS configuration
   template: src={{ item.src }} dest=/etc/powerdns/{{ item.dest }} owner=root group=root mode=0644
   loop:
     - {src: pdns.conf.j2, dest: pdns.conf}
-    - {src: geo.yml.j2, dest: geo.yml}
     - {src: dnsupdate-policy.lua.j2, dest: dnsupdate-policy.lua}
   notify: restart powerdns
 
 - name: create directory for sqlite3 dbs
   file: path=/var/lib/powerdns state=directory owner=powerdns group=powerdns mode=0755
-  when: geomirror_acme_challenge
 
-- name: initialize sqlite3 database for _acme-challenge zone
+- name: initialize sqlite3 database for _acme-challenge zones
   command: sqlite3 -init /usr/share/doc/powerdns/schema.sqlite3.sql /var/lib/powerdns/pdns.sqlite3 ""
   become: true
   become_user: powerdns
   args:
     creates: /var/lib/powerdns/pdns.sqlite3
-  register: init
-  when: geomirror_acme_challenge
 
-- name: create _acme-challenge zone
-  command: "{{ item }}"
-  loop:
-    - pdnsutil create-zone _acme-challenge.{{ geo_mirror_domain }} mirror.pkgbuild.com
-    - pdnsutil replace-rrset _acme-challenge.{{ geo_mirror_domain }} @ SOA "mirror.pkgbuild.com. root.archlinux.org. 0 10800 3600 604800 3600"
+- name: create _acme-challenge zones
+  shell: |
+    pdnsutil create-zone _acme-challenge.{{ item }} {{ inventory_hostname }}
+    pdnsutil replace-rrset _acme-challenge.{{ item }} @ SOA "{{ inventory_hostname }}. root.archlinux.org. 0 10800 3600 604800 3600"
+  loop: "{{ geo_domains }}"
   become: true
   become_user: powerdns
-  when: init.changed
+  changed_when: false
 
 - name: import TSIG key (for certbot)
   command: pdnsutil import-tsig-key {{ certbot_rfc2136_key }} {{ certbot_rfc2136_algorithm }} {{ certbot_rfc2136_secret }}
   changed_when: false
-  when: geomirror_acme_challenge
 
 - name: open powerdns ipv4 port for monitoring.archlinux.org
   ansible.posix.firewalld: zone=wireguard state=enabled permanent=true immediate=yes
diff --git a/roles/geomirror/templates/dnsupdate-policy.lua.j2 b/roles/acme_dns_challenge/templates/dnsupdate-policy.lua.j2
similarity index 81%
rename from roles/geomirror/templates/dnsupdate-policy.lua.j2
rename to roles/acme_dns_challenge/templates/dnsupdate-policy.lua.j2
index b9185cc5a8746921f0223f34e44c4b40e533ebd1..50fc8a2223780aedcdd8a6618e25d9afd50358f2 100644
--- a/roles/geomirror/templates/dnsupdate-policy.lua.j2
+++ b/roles/acme_dns_challenge/templates/dnsupdate-policy.lua.j2
@@ -1,6 +1,11 @@
+#jinja2: lstrip_blocks: True
 -- Based on https://github.com/PowerDNS/pdns/wiki/Lua-Examples-(Authoritative)#updatepolicy-access-control-for-rfc2136-dynamic-updates
 function updatepolicy(input)
-  acme_challenge_rrname = "_acme-challenge.{{ geo_mirror_domain }}."
+  valid_rrnames = {
+    {% for domain in geo_domains %}
+    ["_acme-challenge.{{ domain }}."]=true,
+    {% endfor %}
+  }
 
   -- only allow updates from our servers
   mynetworks = newNMG()
@@ -26,7 +31,7 @@ function updatepolicy(input)
   end
 
   -- only accept TXT record updates for _acme_challenge
-  if input:getQType() == pdns.TXT and input:getQName():toString() == acme_challenge_rrname
+  if input:getQType() == pdns.TXT and valid_rrnames[input:getQName():toString()]
   then
     pdnslog("updatepolicy: query checks successful", pdns.loglevels.Info)
     return true
diff --git a/roles/acme_dns_challenge/templates/pdns.conf.j2 b/roles/acme_dns_challenge/templates/pdns.conf.j2
new file mode 100644
index 0000000000000000000000000000000000000000..49b1264784f201ed751aea70f970eba05b62f7fc
--- /dev/null
+++ b/roles/acme_dns_challenge/templates/pdns.conf.j2
@@ -0,0 +1,10 @@
+setgid=powerdns
+setuid=powerdns
+local-address={{ ipv4_address }},{{ ipv6_address }}
+webserver=yes
+webserver-address=0.0.0.0
+webserver-allow-from=127.0.0.1,::1,{{ hostvars['monitoring.archlinux.org']['wireguard_address'] }}
+launch=gsqlite3
+gsqlite3-database=/var/lib/powerdns/pdns.sqlite3
+dnsupdate=yes
+lua-dnsupdate-policy-script=/etc/powerdns/dnsupdate-policy.lua
diff --git a/roles/geo_dns/handlers/main.yml b/roles/geo_dns/handlers/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ee953235084233b13220d92752900f740d077485
--- /dev/null
+++ b/roles/geo_dns/handlers/main.yml
@@ -0,0 +1,3 @@
+---
+- name: restart powerdns
+  service: name=pdns state=restarted
diff --git a/roles/geomirror/meta/main.yml b/roles/geo_dns/meta/main.yml
similarity index 100%
rename from roles/geomirror/meta/main.yml
rename to roles/geo_dns/meta/main.yml
diff --git a/roles/geo_dns/tasks/main.yml b/roles/geo_dns/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c7a251f13e6803efb8130c02bc4fe9a832074656
--- /dev/null
+++ b/roles/geo_dns/tasks/main.yml
@@ -0,0 +1,22 @@
+---
+- name: install powerdns and geoip
+  pacman: name=powerdns,libmaxminddb,geoip,yaml-cpp state=present
+
+- name: install PowerDNS configuration
+  template: src={{ item.src }} dest=/etc/powerdns/{{ item.dest }} owner=root group=root mode=0644
+  loop:
+    - {src: pdns.conf.j2, dest: pdns.conf}
+    - {src: geo.yml.j2, dest: geo.yml}
+  notify: restart powerdns
+
+- name: open powerdns ipv4 port for monitoring.archlinux.org
+  ansible.posix.firewalld: zone=wireguard state=enabled permanent=true immediate=yes
+    rich_rule="rule family=ipv4 source address={{ hostvars['monitoring.archlinux.org']['wireguard_address'] }} port protocol=tcp port=8081 accept"
+  tags:
+    - firewall
+
+- name: open firewall hole
+  ansible.posix.firewalld: service=dns permanent=true state=enabled immediate=yes
+
+- name: start and enable powerdns
+  systemd: name=pdns.service enabled=yes daemon_reload=yes state=started
diff --git a/roles/geomirror/templates/geo.yml.j2 b/roles/geo_dns/templates/geo.yml.j2
similarity index 55%
rename from roles/geomirror/templates/geo.yml.j2
rename to roles/geo_dns/templates/geo.yml.j2
index 82533fff92f513f639729dd921112194263339c0..485f62b27754910b79bdb082ef9778475c53a8ec 100644
--- a/roles/geomirror/templates/geo.yml.j2
+++ b/roles/geo_dns/templates/geo.yml.j2
@@ -1,12 +1,13 @@
 #jinja2:lstrip_blocks: True
 ---
 domains:
-  - domain: {{ geo_mirror_domain }}
+  {% for domain in geo_domains %}
+  - domain: {{ domain }}
     ttl: 3600
     records:
-      {{ geo_mirror_domain }}:
-        - soa: mirror.pkgbuild.com. root.archlinux.org. 2022042701 3600 1800 604800 3600
-        {% for host in groups['geo_mirrors'] + ['mirror.pkgbuild.com'] %}
+      {{ domain }}:
+        - soa: {{ groups['geo_mirrors'] | first }}. root.archlinux.org. 2022042701 3600 1800 604800 3600
+        {% for host in groups['geo_mirrors'] %}
         - ns:
             ttl: 86400
             content: {{ host }}
@@ -14,16 +15,15 @@ domains:
         - lua:
             ttl: 300
             content: >
-              A "ifurlup('https://{{ geo_mirror_domain }}/lastupdate',
+              A "ifurlup('https://{{ domain }}{{ geo_health_check_paths[domain] | default('/') }}',
               {'{{ groups['geo_mirrors'] | map('extract', hostvars, ['ipv4_address']) | join("', '") }}'},
               {selector='pickclosest', useragent='pdns on {{ inventory_hostname }}'})"
         - lua:
             ttl: 300
             content: >
-              AAAA "ifurlup('https://{{ geo_mirror_domain }}/lastupdate',
+              AAAA "ifurlup('https://{{ domain }}{{ geo_health_check_paths[domain] | default('/') }}',
               {'{{ groups['geo_mirrors'] | map('extract', hostvars, ['ipv6_address']) | join("', '") }}'},
               {selector='pickclosest', useragent='pdns on {{ inventory_hostname }}'})"
-      {% if not geomirror_acme_challenge %}
-      _acme-challenge.{{ geo_mirror_domain }}:
-        - ns: mirror.pkgbuild.com
-      {% endif %}
+      _acme-challenge.{{ domain }}:
+        - ns: {{ geo_acme_dns_challenge_ns }}
+  {% endfor %}
diff --git a/roles/geomirror/templates/pdns.conf.j2 b/roles/geo_dns/templates/pdns.conf.j2
similarity index 64%
rename from roles/geomirror/templates/pdns.conf.j2
rename to roles/geo_dns/templates/pdns.conf.j2
index 6539ab25d0565583c5e0e69d2207d7236a473a4b..6b1d467c5f9cde1364e0b7ba101acd1b6dd7df80 100644
--- a/roles/geomirror/templates/pdns.conf.j2
+++ b/roles/geo_dns/templates/pdns.conf.j2
@@ -4,14 +4,7 @@ local-address={{ ipv4_address }},{{ ipv6_address }}
 webserver=yes
 webserver-address=0.0.0.0
 webserver-allow-from=127.0.0.1,::1,{{ hostvars['monitoring.archlinux.org']['wireguard_address'] }}
-{% if geomirror_acme_challenge %}
-launch=geoip,gsqlite3
-gsqlite3-database=/var/lib/powerdns/pdns.sqlite3
-dnsupdate=yes
-lua-dnsupdate-policy-script=/etc/powerdns/dnsupdate-policy.lua
-{% else %}
 launch=geoip
-{% endif %}
 geoip-database-files=/var/lib/GeoIP/GeoLite2-City.mmdb
 geoip-zones-file=/etc/powerdns/geo.yml
 enable-lua-records
diff --git a/roles/geomirror/defaults/main.yml b/roles/geomirror/defaults/main.yml
deleted file mode 100644
index b82cec9d855633a7dac50735bf4f953c5e12d1e3..0000000000000000000000000000000000000000
--- a/roles/geomirror/defaults/main.yml
+++ /dev/null
@@ -1,2 +0,0 @@
----
-geomirror_acme_challenge: false
diff --git a/roles/prometheus/defaults/main.yml b/roles/prometheus/defaults/main.yml
index 203b1101cf26e499637f56220221202a1cf422e7..b063259ac2643a46c1e581ce719bebf239261557 100644
--- a/roles/prometheus/defaults/main.yml
+++ b/roles/prometheus/defaults/main.yml
@@ -75,8 +75,8 @@ blackbox_targets:
   smtp_starttls:
     - mail.archlinux.org:25
     - lists.archlinux.org:25
-  dns_geomirror_a: "{{ groups['geo_mirrors'] + ['mirror.pkgbuild.com'] }}"
-  dns_geomirror_aaaa: "{{ groups['geo_mirrors'] + ['mirror.pkgbuild.com'] }}"
+  geo_dns_geo.mirror.pkgbuild.com_a: "{{ groups['geo_mirrors'] }}"
+  geo_dns_geo.mirror.pkgbuild.com_aaaa: "{{ groups['geo_mirrors'] }}"
 matrix_metrics_endpoints:
   - homeserver
   - appservice
diff --git a/roles/prometheus/templates/prometheus.yml.j2 b/roles/prometheus/templates/prometheus.yml.j2
index 96a38e31851fa6eebfef451b0b1485bc7641adb3..e700cb6373674905425f0eea07954ca23b98a53f 100644
--- a/roles/prometheus/templates/prometheus.yml.j2
+++ b/roles/prometheus/templates/prometheus.yml.j2
@@ -74,7 +74,7 @@ scrape_configs:
 
   - job_name: 'powerdns'
     static_configs:
-      {% for host in groups['geo_mirrors'] + ['mirror.pkgbuild.com'] %}
+      {% for host in groups['geo_mirrors'] + [geo_acme_dns_challenge_ns] %}
       - targets: ['{{ hostvars[host]['wireguard_address'] }}:8081']
         labels:
           instance: "{{ host }}"
diff --git a/roles/prometheus_exporters/templates/blackbox.yml.j2 b/roles/prometheus_exporters/templates/blackbox.yml.j2
index 9ba1a2a51a5545bd049f3b3bbc045efe1a0c019b..9f78648bfee69bf9f05b8be4f2528a121ec5542b 100644
--- a/roles/prometheus_exporters/templates/blackbox.yml.j2
+++ b/roles/prometheus_exporters/templates/blackbox.yml.j2
@@ -1,3 +1,4 @@
+#jinja2: lstrip_blocks: True
 modules:
   http_prometheus:
     prober: http
@@ -24,23 +25,25 @@ modules:
         - send: "EHLO prober\r"
         - expect: "^250"
         - send: "QUIT\r"
-  dns_geomirror_a:
+  {% for domain in geo_domains %}
+  geo_dns_{{ domain }}_a:
     prober: dns
     timeout: 5s
     dns:
-      query_name: geo.mirror.pkgbuild.com
+      query_name: {{ domain }}
       query_type: A
       preferred_ip_protocol: ip4
       validate_answer_rrs:
         fail_if_not_matches_regexp:
-          - geo\.mirror\.pkgbuild\.com\.\t.*\tIN\tA\t({{ groups['geo_mirrors'] | map('extract', hostvars, ['ipv4_address']) | join('|') | replace('.', '\.') }})
-  dns_geomirror_aaaa:
+          - {{ domain | replace('.', '\.') }}\.\t.*\tIN\tA\t({{ groups['geo_mirrors'] | map('extract', hostvars, ['ipv4_address']) | join('|') | replace('.', '\.') }})
+  geo_dns_{{ domain }}_aaaa:
     prober: dns
     timeout: 5s
     dns:
-      query_name: geo.mirror.pkgbuild.com
+      query_name: {{ domain }}
       query_type: AAAA
       preferred_ip_protocol: ip6
       validate_answer_rrs:
         fail_if_not_matches_regexp:
-          - geo\.mirror\.pkgbuild\.com\.\t.*\tIN\tAAAA\t({{ groups['geo_mirrors'] | map('extract', hostvars, ['ipv6_address']) | join('|') }})
+          - {{ domain | replace('.', '\.') }}\.\t.*\tIN\tAAAA\t({{ groups['geo_mirrors'] | map('extract', hostvars, ['ipv6_address']) | join('|') }})
+  {% endfor %}
diff --git a/tf-stage1/archlinux.tf b/tf-stage1/archlinux.tf
index 76378826dca4d92262dd681efef4bd7cafa28556..e336ed0ec66c71b9ea30a6913da3bb9d70ba0c14 100644
--- a/tf-stage1/archlinux.tf
+++ b/tf-stage1/archlinux.tf
@@ -364,6 +364,14 @@ locals {
       ipv6_address = hcloud_server.machine["homedir.archlinux.org"].ipv6_address
     }
   }
+
+  # Domains served by machines in the geo_mirrors group
+  geo_domains = {
+    "geo.mirror.pkgbuild.com" = {
+      zone_id = hetznerdns_zone.pkgbuild.id
+      name    = "geo.mirror"
+    }
+  }
 }
 
 resource "hetznerdns_zone" "archlinux" {
@@ -431,38 +439,6 @@ resource "hetznerdns_record" "pkgbuild_com_origin_txt" {
   type    = "TXT"
 }
 
-resource "hetznerdns_record" "pkgbuild_com_geo_mirror_ns1" {
-  zone_id = hetznerdns_zone.pkgbuild.id
-  name    = "geo.mirror"
-  value   = "mirror.pkgbuild.com."
-  type    = "NS"
-  ttl     = 86400
-}
-
-resource "hetznerdns_record" "pkgbuild_com_geo_mirror_ns2" {
-  zone_id = hetznerdns_zone.pkgbuild.id
-  name    = "geo.mirror"
-  value   = "asia.mirror.pkgbuild.com."
-  type    = "NS"
-  ttl     = 86400
-}
-
-resource "hetznerdns_record" "pkgbuild_com_geo_mirror_ns3" {
-  zone_id = hetznerdns_zone.pkgbuild.id
-  name    = "geo.mirror"
-  value   = "america.mirror.pkgbuild.com."
-  type    = "NS"
-  ttl     = 86400
-}
-
-resource "hetznerdns_record" "pkgbuild_com_geo_mirror_ns4" {
-  zone_id = hetznerdns_zone.pkgbuild.id
-  name    = "geo.mirror"
-  value   = "europe.mirror.pkgbuild.com."
-  type    = "NS"
-  ttl     = 86400
-}
-
 resource "hetznerdns_record" "archlinux_org_origin_caa" {
   zone_id = hetznerdns_zone.archlinux.id
   name    = "@"
diff --git a/tf-stage1/templates.tf b/tf-stage1/templates.tf
index 7b7de1dcc4ef498843559ee2b4294dfaccc2560e..24df546277ab610aab1bebb9c3498207f217f6ac 100644
--- a/tf-stage1/templates.tf
+++ b/tf-stage1/templates.tf
@@ -142,3 +142,33 @@ resource "hetznerdns_record" "machine_aaaa" {
   value   = hcloud_server.machine[each.key].ipv6_address
   type    = "AAAA"
 }
+
+resource "hetznerdns_record" "geo_ns1" {
+  for_each = local.geo_domains
+
+  zone_id = each.value.zone_id
+  name    = each.value.name
+  value   = "asia.mirror.pkgbuild.com."
+  type    = "NS"
+  ttl     = 86400
+}
+
+resource "hetznerdns_record" "geo_ns2" {
+  for_each = local.geo_domains
+
+  zone_id = each.value.zone_id
+  name    = each.value.name
+  value   = "america.mirror.pkgbuild.com."
+  type    = "NS"
+  ttl     = 86400
+}
+
+resource "hetznerdns_record" "geo_ns3" {
+  for_each = local.geo_domains
+
+  zone_id = each.value.zone_id
+  name    = each.value.name
+  value   = "europe.mirror.pkgbuild.com."
+  type    = "NS"
+  ttl     = 86400
+}