diff --git a/biscuit/core/settings.py b/biscuit/core/settings.py
index 5dfb91b6108f3601abd4948cd718b828215b82cc..27955545c8ac5b22a716b22d4db764d43b7bb200 100644
--- a/biscuit/core/settings.py
+++ b/biscuit/core/settings.py
@@ -54,6 +54,8 @@ INSTALLED_APPS = [
     'fa',
     'django_any_js',
     'django_tables2',
+    'django_ipware',
+    'maintenance_mode',
     'menu_generator',
     'phonenumber_field',
     'debug_toolbar',
@@ -88,6 +90,7 @@ MIDDLEWARE = [
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
     'easyaudit.middleware.easyaudit.EasyAuditMiddleware',
+    'maintenance_mode.middleware.MaintenanceModeMiddleware',
 ]
 
 ROOT_URLCONF = 'biscuit.core.urls'
@@ -103,6 +106,7 @@ TEMPLATES = [
                 'django.template.context_processors.request',
                 'django.contrib.auth.context_processors.auth',
                 'django.contrib.messages.context_processors.messages',
+                'maintenance_mode.context_processors.maintenance_mode',
                 'settings_context_processor.context_processors.settings'
             ],
         },
@@ -240,4 +244,9 @@ ADMINS = _settings.get('admins', [])
 
 TEMPLATE_VISIBLE_SETTINGS = ['ADMINS']
 
+MAINTENANCE_MODE = _settings.get('maintenance.activate', 'None')
+MAINTENANCE_MODE_IGNORE_IP_ADDRESSES = _settings.get('maintenance.ignore_ip_addresses', ())
+MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = 'ipware.ip.get_ip'
+MAINTENANCE_MODE_IGNORE_SUPERUSER = True
+
 _settings.populate_obj(sys.modules[__name__])
diff --git a/biscuit/core/urls.py b/biscuit/core/urls.py
index 93b96080c2f4ab87351da1e3e25201da650287fb..ab77a04a5d7d6dfb6856ac076c9c69a451cffb33 100644
--- a/biscuit/core/urls.py
+++ b/biscuit/core/urls.py
@@ -21,11 +21,13 @@ urlpatterns = [
     path('group/<int:id_>', views.group,
          {'template': 'full'}, name='group_by_id'),
     path('', views.index, name='index'),
+    path('maintenance-mode/', include('maintenance_mode.urls')),
 ]
 
 # Custom error pages
 handler404 = views.error_handler(404)
 handler500 = views.error_handler(500)
+handler503 = views.error_handler(503)
 
 # Serve javascript-common if in development
 if settings.DEBUG:
diff --git a/biscuit/core/views.py b/biscuit/core/views.py
index d661c912de532586b5c3070170b1b1c24324d104..ad982e2cb876bd8ae1510f6eabe0b023c5acc892 100644
--- a/biscuit/core/views.py
+++ b/biscuit/core/views.py
@@ -30,6 +30,9 @@ def error_handler(status: int) -> Callable[..., HttpResponse]:
         elif status == 500:
             context['caption'] = _('Internal server error')
             context['message'] = _('An unexpected error has occurred.')
+        elif status == 503:
+            context['caption'] = _('Maintenance mode')
+            context['message'] = _('The maintenance mode is currently enabled. Please try again later.')
 
         return render(request, 'error.html', context, status=status)
 
diff --git a/pyproject.toml b/pyproject.toml
index 1803857d582b70615ba1a3d47abcfc26effef93f..210583aa687e2b50514f482d561175dd89bbf9ac 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -38,6 +38,8 @@ colour = "^0.1.5"
 dynaconf = {version = "^2.0", extras = ["yaml", "toml", "ini"]}
 django-settings-context-processor = "^0.2"
 django-auth-ldap = { version = "^2.0", optional = true }
+django-maintenance-mode = "^0.13.3"
+django-ipware = "^2.1"
 
 [tool.poetry.extras]
 ldap = ["django-auth-ldap"]