diff --git a/deployment/apache.maintenance-template.conf b/deployment/apache.maintenance-template.conf
new file mode 100644
index 0000000000000000000000000000000000000000..a246186f78ac942ad819481b26dfacefaa8a43d2
--- /dev/null
+++ b/deployment/apache.maintenance-template.conf
@@ -0,0 +1,24 @@
+<VirtualHost *:80>
+    ServerName evap
+
+    DocumentRoot /var/www/html
+
+    RewriteEngine on
+    RewriteCond %{DOCUMENT_ROOT}/maintenance.html -f
+    RewriteCond %{SCRIPT_FILENAME} !maintenance.html
+    RewriteRule !\.(png|css|svg)$ /maintenance.html [R=503,L]
+    RewriteRule maintenance\.css$ /maintenance.css
+    RewriteRule triangles_gray\.svg$ /background_gray.svg
+    RewriteRule triangles_color\.svg$ /background_color.svg
+    RewriteRule favicon\.png$ /favicon.png
+    ErrorDocument 503 /maintenance.html
+    Header Set Cache-Control "max-age=0, no-store"
+
+    ErrorLog /var/log/apache2/error.log
+
+    # Possible values include: debug, info, notice, warn, error, crit,
+    # alert, emerg.
+    LogLevel info
+
+    CustomLog /var/log/apache2/access.log combined
+</VirtualHost>
diff --git a/deployment/disable_maintenance_mode.sh b/deployment/disable_maintenance_mode.sh
new file mode 100755
index 0000000000000000000000000000000000000000..70d8d44d0088d5d206a458e498cd5cc5c6ece04a
--- /dev/null
+++ b/deployment/disable_maintenance_mode.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+set -e # abort on error
+
+rm -f /var/www/html/maintenance.css
+rm -f /var/www/html/background_gray.svg
+rm -f /var/www/html/background_color.svg
+rm -f /var/www/html/favicon.png
+rm -f /var/www/html/maintenance.html
+a2ensite -q evap.conf
+a2dissite -q evap-maintenance.conf
+service apache2 restart
+echo "Maintenance mode disabled."
diff --git a/deployment/enable_maintenance_mode.sh b/deployment/enable_maintenance_mode.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a8f97f1deac0815d3d3e2b80b66878ffe75255d0
--- /dev/null
+++ b/deployment/enable_maintenance_mode.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+set -e # abort on error
+
+cp /opt/evap/evap/static/css/evap.css /var/www/html/maintenance.css
+cp /opt/evap/evap/static/images/triangles_gray.svg /var/www/html/background_gray.svg
+cp /opt/evap/evap/static/images/triangles_color.svg /var/www/html/background_color.svg
+cp /opt/evap/evap/static/images/favicon_64.png /var/www/html/favicon.png
+cp /opt/evap/evap/static/maintenance/maintenance.html /var/www/html/maintenance.html
+a2ensite -q evap-maintenance.conf
+a2dissite -q evap.conf
+service apache2 restart
+echo "Maintenance mode enabled."
diff --git a/deployment/provision_vagrant_vm.sh b/deployment/provision_vagrant_vm.sh
index d619787dd1108c4eb1384521d72f463f38c204de..af9de4a9d848ffae1c14557d6f97a884b4649f03 100755
--- a/deployment/provision_vagrant_vm.sh
+++ b/deployment/provision_vagrant_vm.sh
@@ -47,6 +47,8 @@ a2enmod headers
 cp $REPO_FOLDER/deployment/wsgi.template.conf /etc/apache2/mods-available/wsgi.load
 sed -i -e "s=\${ENV_FOLDER}=$ENV_FOLDER=" /etc/apache2/mods-available/wsgi.load # note this uses '=' as alternate delimiter
 a2enmod wsgi
+a2enmod rewrite
+cp $REPO_FOLDER/deployment/apache.maintenance-template.conf /etc/apache2/sites-available/evap-maintenance.conf
 cp $REPO_FOLDER/deployment/apache.template.conf /etc/apache2/sites-available/evap.conf
 sed -i -e "s=\${ENV_FOLDER}=$ENV_FOLDER=" /etc/apache2/sites-available/evap.conf
 sed -i -e "s=\${REPO_FOLDER}=$REPO_FOLDER=" /etc/apache2/sites-available/evap.conf
diff --git a/deployment/update_production.sh b/deployment/update_production.sh
index d2a10ada0e48bc27d5ca8dda48684c21095d17cd..94b6870e48651afbeba5812a81d5113ee7b5f729 100755
--- a/deployment/update_production.sh
+++ b/deployment/update_production.sh
@@ -34,7 +34,7 @@ sudo -H -u $USERNAME git fetch
 # Note that apache should not be running during most of the upgrade,
 # since then e.g. the backup might be incomplete or the code does not
 # match the database layout, or https://github.com/e-valuation/EvaP/issues/1237.
-[[ -z "$GITHUB_WORKFLOW" ]] && sudo service apache2 stop
+[[ -z "$GITHUB_WORKFLOW" ]] && sudo ./deployment/enable_maintenance_mode.sh
 
 sudo -H -u "$USERNAME" "$ENVDIR/bin/python" manage.py dumpdata --natural-foreign --natural-primary --all -e contenttypes -e auth.Permission --indent 2 --output "$FILENAME"
 
@@ -51,7 +51,7 @@ sudo -H -u "$USERNAME" "$ENVDIR/bin/python" manage.py migrate
 sudo -H -u "$USERNAME" "$ENVDIR/bin/python" manage.py clear_cache
 sudo -H -u "$USERNAME" "$ENVDIR/bin/python" manage.py refresh_results_cache
 
-[[ -z "$GITHUB_WORKFLOW" ]] && sudo service apache2 start
+[[ -z "$GITHUB_WORKFLOW" ]] && sudo ./deployment/disable_maintenance_mode.sh
 
 { set +x; } 2>/dev/null # don't print the echo command, and don't print the 'set +x' itself
 
diff --git a/evap/static/maintenance/maintenance.html b/evap/static/maintenance/maintenance.html
new file mode 100644
index 0000000000000000000000000000000000000000..2a8d80361a0b30741d015aefacfe30e7cdc7fc53
--- /dev/null
+++ b/evap/static/maintenance/maintenance.html
@@ -0,0 +1,36 @@
+<html>
+    <head>
+        <meta charset="utf-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
+        <title>EvaP</title>
+        <link rel="stylesheet" href="maintenance.css" />
+        <link rel="icon" type="image/png" sizes="64x64" href="favicon.png" />
+    </head>
+    <body>
+        <div class="container pt-4 d-flex h-100">
+            <div class="jumbotron-evap my-auto">
+                <div class="row">
+                    <div class="col-md-5">
+                        <h1 class="display-4">Wartung</h1>
+                        <hr class="my-4" />
+                        <div>
+                            EvaP wird gerade auf den neuesten Stand gebracht. Bitte versuchen Sie es in ein paar Minuten noch einmal.
+                        </div>
+                    </div>
+                    <div class="col-md-2 d-flex py-4">
+                        <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill="#495057" class="mx-auto my-auto" viewBox="0 0 16 16">
+                            <path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
+                        </svg>
+                    </div>
+                    <div class="col-md-5">
+                        <h1 class="display-4">Maintenance</h1>
+                        <hr class="my-4" />
+                        <div>
+                            EvaP is being updated right now. Please try again in a few minutes.
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </body>
+</html>