diff --git a/docs/maintenance.md b/docs/maintenance.md
index 2b410eb2e33a613d0e99a1221aebdff9e23f2fef..c7fbd82088a4bcb7fd23cadbbb65075b837319c9 100644
--- a/docs/maintenance.md
+++ b/docs/maintenance.md
@@ -27,6 +27,7 @@ The basic configuration looks like this:
     service_name: "<service name>"
     service_domain: "{{ service_domain }}"
     service_alternate_domains: []
+    service_legacy_domains: []
     service_nginx_conf: "{{ service_nginx_conf }}"
   when: maintenance is defined
 ```
diff --git a/roles/archweb/defaults/main.yml b/roles/archweb/defaults/main.yml
index 7530dda6a8e7d484c446a0c18cde69b59ed88fd8..d2ee0c9b471f501aacd84766cfbafec794de0785 100644
--- a/roles/archweb/defaults/main.yml
+++ b/roles/archweb/defaults/main.yml
@@ -1,6 +1,7 @@
 archweb_dir: '/srv/http/archweb'
 archweb_domain: 'archlinux.org'
-archweb_alternate_domains: ['www.archlinux.org', 'master-key.archlinux.org', 'dev.archlinux.org', 'packages.archlinux.org', 'ipxe.archlinux.org', 'planet.archlinux.org']
+archweb_alternate_domains: ['www.archlinux.org', 'master-key.archlinux.org', 'dev.archlinux.org', 'packages.archlinux.org', 'planet.archlinux.org']
+archweb_legacy_domains: ['ipxe.archlinux.org']
 archweb_domains_redirects:
         'www.archlinux.org': '$request_uri'
         'master-key.archlinux.org': '/master-keys/'
diff --git a/roles/archweb/tasks/main.yml b/roles/archweb/tasks/main.yml
index aeb233fc281309b3a69ec09f6ae2013c77544fbe..487dc715a150d92900643c1c61ae4aa35b054789 100644
--- a/roles/archweb/tasks/main.yml
+++ b/roles/archweb/tasks/main.yml
@@ -5,6 +5,7 @@
     service_name: "site"
     service_domain: "{{ archweb_domain }}"
     service_alternate_domains: "{{ archweb_alternate_domains }}"
+    service_legacy_domains: "{{ archweb_legacy_domains }}"
     service_nginx_conf: "{{ archweb_nginx_conf }}"
     service_nginx_template: "maintenance-nginx.d.conf.j2"
   when: maintenance is defined and archweb_site
@@ -29,6 +30,15 @@
     domains: "{{ [archweb_domain] + archweb_alternate_domains }}"
   when: archweb_site | bool and maintenance is not defined
 
+- name: Create legacy ssl cert
+  include_role:
+    name: certificate
+  vars:
+    cert_name: "{{ archweb_domain }}_legacy"
+    domains: "{{ archweb_legacy_domains }}"
+    legacy: true
+  when: archweb_site | bool and maintenance is not defined
+
 - name: Set up nginx
   template: src=nginx.d.conf.j2 dest="{{ archweb_nginx_conf }}" owner=root group=root mode=644
   notify: Reload nginx
diff --git a/roles/archweb/templates/ipxe.archlinux.org.j2 b/roles/archweb/templates/ipxe.archlinux.org.j2
index 721b8e3c393b7f8afbf57891cb52806078bdb68e..2df4e1d6007c854437a4340c8ee5e6e6e6aeaf96 100644
--- a/roles/archweb/templates/ipxe.archlinux.org.j2
+++ b/roles/archweb/templates/ipxe.archlinux.org.j2
@@ -27,9 +27,9 @@ server {
 
     ssl_ciphers AES128-SHA:AES256-SHA:AES128-SHA256:AES256-SHA256;
 
-    ssl_certificate      /etc/letsencrypt/live/{{ archweb_domain }}/fullchain.pem;
-    ssl_certificate_key  /etc/letsencrypt/live/{{ archweb_domain }}/privkey.pem;
-    ssl_trusted_certificate /etc/letsencrypt/live/{{ archweb_domain }}/chain.pem;
+    ssl_certificate      /etc/letsencrypt/live/{{ archweb_domain }}_legacy/fullchain.pem;
+    ssl_certificate_key  /etc/letsencrypt/live/{{ archweb_domain }}_legacy/privkey.pem;
+    ssl_trusted_certificate /etc/letsencrypt/live/{{ archweb_domain }}_legacy/chain.pem;
 
     location /releng/netboot/ {
         access_log   /var/log/nginx/{{ archweb_domain }}/access.log main;
diff --git a/roles/archweb/templates/maintenance-nginx.d.conf.j2 b/roles/archweb/templates/maintenance-nginx.d.conf.j2
index 4ce5ca758378321dc9484e5e8591d33426eaa108..6eddee94891bc5189a37bcfc1578d3fdde6dd19f 100644
--- a/roles/archweb/templates/maintenance-nginx.d.conf.j2
+++ b/roles/archweb/templates/maintenance-nginx.d.conf.j2
@@ -2,9 +2,7 @@ upstream archweb {
     server unix:///run/uwsgi/archweb.sock;
 }
 
-{% if service_alternate_domains %}
-{% for domain in service_alternate_domains %}
-
+{% for domain in service_alternate_domains | default([]) %}
 server {
     listen       80;
     listen       [::]:80;
@@ -18,7 +16,7 @@ server {
 
     location / {
         access_log off;
-        return 301 https://$server_name$request_uri;
+        return 302 https://$server_name$request_uri;
     }
 }
 
@@ -38,16 +36,51 @@ server {
 
     location / {
         access_log off;
-        return 301 https://{{ service_domain }};
+        return 302 https://{{ service_domain }};
     }
 }
+
 {% endfor %}
+{% for domain in service_legacy_domains | default([]) %}
+server {
+    listen       80;
+    listen       [::]:80;
+    server_name  {{ domain }};
+
+    access_log   {{ maintenance_logs_dir }}/{{ service_domain }}-access.log reduced;
+    access_log   {{ maintenance_logs_dir }}/{{ service_domain }}-access.log.json json_reduced;
+    error_log    {{ maintenance_logs_dir }}/{{ service_domain }}-error.log;
+
+    include snippets/letsencrypt.conf;
+
+    location / {
+        access_log off;
+        return 302 https://$server_name$request_uri;
+    }
+}
 
 server {
-{% else %}
+    listen       443 ssl;
+    listen       [::]:443 ssl;
+    http2        on;
+    server_name  {{ domain }};
+
+    access_log   {{ maintenance_logs_dir }}/{{ service_domain }}-access.log reduced;
+    access_log   {{ maintenance_logs_dir }}/{{ service_domain }}-access.log.json json_reduced;
+    error_log    {{ maintenance_logs_dir }}/{{ service_domain }}-error.log;
+
+    ssl_certificate      /etc/letsencrypt/live/{{ service_domain }}_legacy/fullchain.pem;
+    ssl_certificate_key  /etc/letsencrypt/live/{{ service_domain }}_legacy/privkey.pem;
+    ssl_trusted_certificate /etc/letsencrypt/live/{{ service_domain }}_legacy/chain.pem;
+
+    location / {
+        access_log off;
+        return 302 https://{{ service_domain }};
+    }
+}
 
+{% endfor %}
 server {
-{% endif %}
     listen       80;
     listen       [::]:80;
     server_name  {{ service_domain }};
@@ -60,7 +93,7 @@ server {
 
     location / {
         access_log off;
-        return 301 https://$server_name$request_uri;
+        return 302 https://$server_name$request_uri;
     }
 }
 
diff --git a/roles/archwiki/tasks/main.yml b/roles/archwiki/tasks/main.yml
index 7ab910b99a2e0063a5e99d2cc2414c6e3621d9a2..9140317c71e77a078cf0c94e0184de3d2ae2c486 100644
--- a/roles/archwiki/tasks/main.yml
+++ b/roles/archwiki/tasks/main.yml
@@ -4,7 +4,6 @@
   vars:
     service_name: "wiki"
     service_domain: "{{ archwiki_domain }}"
-    service_alternate_domains: []
     service_nginx_conf: "{{ archwiki_nginx_conf }}"
   when: maintenance is defined
 
diff --git a/roles/certbot/files/certbot-renewal.service b/roles/certbot/files/certbot-renewal.service
index b9d5844830c3717652b7f2f232695ebb94632eff..846b94a5fa88a1953a04f56baccb113fc3101dee 100644
--- a/roles/certbot/files/certbot-renewal.service
+++ b/roles/certbot/files/certbot-renewal.service
@@ -3,7 +3,7 @@ Description=Let's Encrypt renewal
 
 [Service]
 Type=oneshot
-ExecStart=/usr/bin/certbot renew --key-type ecdsa \
+ExecStart=/usr/bin/certbot renew                  \
     --no-random-sleep-on-renew                    \
     --pre-hook   "/etc/letsencrypt/hook.sh pre"   \
     --post-hook  "/etc/letsencrypt/hook.sh post"  \
diff --git a/roles/certificate/tasks/main.yml b/roles/certificate/tasks/main.yml
index 4d76a141803f0359f4ff1c38d3318c222bf5b6bf..c1d3f98f1fe6d1c73164fef60c2fde60d22320bc 100644
--- a/roles/certificate/tasks/main.yml
+++ b/roles/certificate/tasks/main.yml
@@ -1,17 +1,17 @@
-- name: Create ssl cert (HTTP-01)
+- name: Create ssl cert (HTTP-01) named {{ cert_name | default(domains | first) }}
   shell: |
     set -o pipefail
     # We can't start nginx without the certificate and we can't issue a certificate without nginx running.
     # So use Python built-in http.server for the initial certificate issuance
     python -m http.server --directory {{ letsencrypt_validation_dir }} 80 &
     trap "jobs -p | xargs --no-run-if-empty kill" EXIT
-    certbot certonly --email {{ certificate_contact_email }} --agree-tos --key-type ecdsa --renew-by-default --webroot -w {{ letsencrypt_validation_dir }} -d {{ domains | join(' -d ') }}
+    certbot certonly --email {{ certificate_contact_email }} --agree-tos --key-type {{ 'ecdsa' if not (legacy | default(false)) else 'rsa --rsa-key-size 4096' }} --renew-by-default --webroot -w {{ letsencrypt_validation_dir }} -d {{ domains | join(' -d ') }} --cert-name {{ cert_name | default(domains | first) }}
   args:
-    creates: '/etc/letsencrypt/live/{{ domains | first }}/fullchain.pem'
+    creates: '/etc/letsencrypt/live/{{ cert_name | default(domains | first) }}/fullchain.pem'
   when: challenge | default(certificate_challenge) == "HTTP-01"
 
-- name: Create ssl cert (DNS-01)
-  command: certbot certonly --email {{ certificate_contact_email }} --agree-tos --key-type ecdsa --renew-by-default --dns-rfc2136 --dns-rfc2136-credentials /etc/letsencrypt/rfc2136.ini -d {{ domains | join(' -d ') }}
+- name: Create ssl cert (DNS-01) named {{ cert_name | default(domains | first) }}
+  command: certbot certonly --email {{ certificate_contact_email }} --agree-tos --key-type {{ 'ecdsa' if not (legacy | default(false)) else 'rsa --rsa-key-size 4096' }} --renew-by-default --dns-rfc2136 --dns-rfc2136-credentials /etc/letsencrypt/rfc2136.ini -d {{ domains | join(' -d ') }} --cert-name {{ cert_name | default(domains | first) }}
   args:
-    creates: '/etc/letsencrypt/live/{{ domains | first }}/fullchain.pem'
+    creates: '/etc/letsencrypt/live/{{ cert_name | default(domains | first) }}/fullchain.pem'
   when: challenge | default(certificate_challenge) == "DNS-01"
diff --git a/roles/gitlab/tasks/main.yml b/roles/gitlab/tasks/main.yml
index 52d8a6f96147743d60282c3ff8921eb967f45439..8a3b3457835c3ffcccd5461c70b250a9637c3c0c 100644
--- a/roles/gitlab/tasks/main.yml
+++ b/roles/gitlab/tasks/main.yml
@@ -34,7 +34,7 @@
         registry_external_url 'https://registry.archlinux.org'
         nginx['client_max_body_size'] = '10g'
         nginx['listen_addresses'] = {{ gitlab_primary_addresses }}
-        nginx['custom_gitlab_server_config'] = "set $bypass 0;\nif ($remote_addr = \"{{ hostvars['gemini.archlinux.org']['ipv4_address'] }}\") {\nset $bypass 1;\n}\nif ($remote_addr = \"{{hostvars['gemini.archlinux.org']['ipv6_address']}}\") {\nset $bypass 1;\n}\nproxy_set_header Gitlab-Bypass-Rate-Limiting $bypass;\n"
+        nginx['custom_gitlab_server_config'] = "set $bypass 0;\nif ($remote_addr = \"{{ hostvars['gemini.archlinux.org']['ipv4_address'] }}\") {\nset $bypass 1;\n}\nif ($remote_addr = \"{{ hostvars['gemini.archlinux.org']['ipv6_address'] }}\") {\nset $bypass 1;\n}\nproxy_set_header Gitlab-Bypass-Rate-Limiting $bypass;\n"
         registry_nginx['listen_addresses'] = {{ gitlab_primary_addresses }}
         gitlab_pages['inplace_chroot'] = true
         pages_external_url "http://{{ gitlab_domain }}"
diff --git a/roles/maintenance/templates/nginx-maintenance.conf.j2 b/roles/maintenance/templates/nginx-maintenance.conf.j2
index 640c9504b0ae235a16e96ded304fb11b14d6c63d..2493c1b78c2c3b9d177d36e23d6137ae6b2da86d 100644
--- a/roles/maintenance/templates/nginx-maintenance.conf.j2
+++ b/roles/maintenance/templates/nginx-maintenance.conf.j2
@@ -1,6 +1,4 @@
-{% if service_alternate_domains %}
-{% for domain in service_alternate_domains %}
-
+{% for domain in service_alternate_domains | default([]) %}
 server {
     listen       80;
     listen       [::]:80;
@@ -14,7 +12,7 @@ server {
 
     location / {
         access_log off;
-        return 301 https://$server_name$request_uri;
+        return 302 https://$server_name$request_uri;
     }
 }
 
@@ -34,16 +32,51 @@ server {
 
     location / {
         access_log off;
-        return 301 https://{{ service_domain }};
+        return 302 https://{{ service_domain }};
     }
 }
+
 {% endfor %}
+{% for domain in service_legacy_domains | default([]) %}
+server {
+    listen       80;
+    listen       [::]:80;
+    server_name  {{ domain }};
+
+    access_log   {{ maintenance_logs_dir }}/{{ service_domain }}-access.log reduced;
+    access_log   {{ maintenance_logs_dir }}/{{ service_domain }}-access.log.json json_reduced;
+    error_log    {{ maintenance_logs_dir }}/{{ service_domain }}-error.log;
+
+    include snippets/letsencrypt.conf;
+
+    location / {
+        access_log off;
+        return 302 https://$server_name$request_uri;
+    }
+}
 
 server {
-{% else %}
+    listen       443 ssl;
+    listen       [::]:443 ssl;
+    http2        on;
+    server_name  {{ domain }};
+
+    access_log   {{ maintenance_logs_dir }}/{{ service_domain }}-access.log reduced;
+    access_log   {{ maintenance_logs_dir }}/{{ service_domain }}-access.log.json json_reduced;
+    error_log    {{ maintenance_logs_dir }}/{{ service_domain }}-error.log;
+
+    ssl_certificate      /etc/letsencrypt/live/{{ service_domain }}_legacy/fullchain.pem;
+    ssl_certificate_key  /etc/letsencrypt/live/{{ service_domain }}_legacy/privkey.pem;
+    ssl_trusted_certificate /etc/letsencrypt/live/{{ service_domain }}_legacy/chain.pem;
+
+    location / {
+        access_log off;
+        return 302 https://{{ service_domain }};
+    }
+}
 
+{% endfor %}
 server {
-{% endif %}
     listen       80;
     listen       [::]:80;
     server_name  {{ service_domain }};
@@ -56,7 +89,7 @@ server {
 
     location / {
         access_log off;
-        return 301 https://$server_name$request_uri;
+        return 302 https://$server_name$request_uri;
     }
 }
 
diff --git a/roles/security_tracker/tasks/main.yml b/roles/security_tracker/tasks/main.yml
index 4037e58a041b53af28052437222e0821ddbe6ec5..f689f48d07227cbf8292bfcc3ff3ea0928d54e63 100644
--- a/roles/security_tracker/tasks/main.yml
+++ b/roles/security_tracker/tasks/main.yml
@@ -4,7 +4,6 @@
   vars:
     service_name: "security tracker"
     service_domain: "{{ security_tracker_domain }}"
-    service_alternate_domains: []
     service_nginx_conf: "{{ security_tracker_nginx_conf }}"
   when: maintenance is defined