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..f135ab5d05f7184d8766ae1612c0ba5c040f7485 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: geomirror, 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/acme_dns_challenge/handlers/main.yml b/roles/acme_dns_challenge/handlers/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ee953235084233b13220d92752900f740d077485
--- /dev/null
+++ b/roles/acme_dns_challenge/handlers/main.yml
@@ -0,0 +1,3 @@
+---
+- name: restart powerdns
+  service: name=pdns state=restarted
diff --git a/roles/acme_dns_challenge/tasks/main.yml b/roles/acme_dns_challenge/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..77e21e66f38235b8b83515860ae507ac01687c18
--- /dev/null
+++ b/roles/acme_dns_challenge/tasks/main.yml
@@ -0,0 +1,45 @@
+---
+- 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: 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
+
+- 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
+
+- 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
+  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
+
+- 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/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/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/geomirror/tasks/main.yml b/roles/geomirror/tasks/main.yml
index 55c15d03ab13cb32cd6309f8934f24c67ebbefdd..c7a251f13e6803efb8130c02bc4fe9a832074656 100644
--- a/roles/geomirror/tasks/main.yml
+++ b/roles/geomirror/tasks/main.yml
@@ -7,36 +7,8 @@
   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
-  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"
-  become: true
-  become_user: powerdns
-  when: init.changed
-
-- 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
     rich_rule="rule family=ipv4 source address={{ hostvars['monitoring.archlinux.org']['wireguard_address'] }} port protocol=tcp port=8081 accept"
diff --git a/roles/geomirror/templates/geo.yml.j2 b/roles/geomirror/templates/geo.yml.j2
index 82533fff92f513f639729dd921112194263339c0..485f62b27754910b79bdb082ef9778475c53a8ec 100644
--- a/roles/geomirror/templates/geo.yml.j2
+++ b/roles/geomirror/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/geomirror/templates/pdns.conf.j2
index 6539ab25d0565583c5e0e69d2207d7236a473a4b..6b1d467c5f9cde1364e0b7ba101acd1b6dd7df80 100644
--- a/roles/geomirror/templates/pdns.conf.j2
+++ b/roles/geomirror/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/prometheus/defaults/main.yml b/roles/prometheus/defaults/main.yml
index 203b1101cf26e499637f56220221202a1cf422e7..4f53dc492ced11a89aa96ca4473d54850f54106d 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'] }}"
+  dns_geomirror_a: "{{ groups['geo_mirrors'] }}"
+  dns_geomirror_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/tf-stage1/templates.tf b/tf-stage1/templates.tf
index 9b97dd3b8d70ab44936fc9dc847a2baaedbd3f4b..24df546277ab610aab1bebb9c3498207f217f6ac 100644
--- a/tf-stage1/templates.tf
+++ b/tf-stage1/templates.tf
@@ -146,16 +146,6 @@ resource "hetznerdns_record" "machine_aaaa" {
 resource "hetznerdns_record" "geo_ns1" {
   for_each = local.geo_domains
 
-  zone_id = each.value.zone_id
-  name    = each.value.name
-  value   = "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   = "asia.mirror.pkgbuild.com."
@@ -163,7 +153,7 @@ resource "hetznerdns_record" "geo_ns2" {
   ttl     = 86400
 }
 
-resource "hetznerdns_record" "geo_ns3" {
+resource "hetznerdns_record" "geo_ns2" {
   for_each = local.geo_domains
 
   zone_id = each.value.zone_id
@@ -173,7 +163,7 @@ resource "hetznerdns_record" "geo_ns3" {
   ttl     = 86400
 }
 
-resource "hetznerdns_record" "geo_ns4" {
+resource "hetznerdns_record" "geo_ns3" {
   for_each = local.geo_domains
 
   zone_id = each.value.zone_id