diff --git a/README.md b/README.md
index aecc542917badbd777a5d07cd71c9b90d0d525f3..3325b3e654ebed703528482c43b4ae16f9a9a5df 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,7 @@ Please note that we refer to Debian packages here and **not** pip packages.
 - python3-qrcode
 - python3-fido2 (version 0.5.0 or 0.9.1, optional)
 - python3-prometheus-client (optional, needed for metrics)
+- python3-redis (optional, needed for redis support)
 - python3-oauthlib
 - python3-flask-babel
 - python3-argon2
diff --git a/debian/control b/debian/control
index 0e498d6a15f3d2fe548a96bc6204fcc630f7fc79..1c0c87561f45c60373183bbbd5f4c7bfda481b38 100644
--- a/debian/control
+++ b/debian/control
@@ -33,4 +33,5 @@ Recommends:
  nginx,
  python3-mysqldb,
  python3-prometheus-client,
+ python3-redis,
 Description: Web-based user management and single sign-on software
diff --git a/tests/test_ratelimit.py b/tests/test_ratelimit.py
index a10903f4055ecaa93c472d6c12fdb0333cd4e6e7..8965ff75da2c9037ebc7fb2d34a9048f1f691fba 100644
--- a/tests/test_ratelimit.py
+++ b/tests/test_ratelimit.py
@@ -6,7 +6,10 @@ from uffd.models.ratelimit import get_addrkey, format_delay, Ratelimit, Ratelimi
 
 from utils import UffdTestCase
 
-class TestRatelimit(UffdTestCase):
+class TestRatelimitDB(UffdTestCase):
+	def setUpConfig(self, config):
+		config['REDIS_HOST'] = False
+
 	def test_limiting(self):
 		cases = [
 			(1*60, 3),
@@ -48,3 +51,18 @@ class TestRatelimit(UffdTestCase):
 		self.assertIsInstance(format_delay(120), str)
 		self.assertIsInstance(format_delay(3600), str)
 		self.assertIsInstance(format_delay(4000), str)
+
+class TestRatelimitRedis(TestRatelimitDB):
+	def setUpConfig(self, config):
+		import redis
+		config['REDIS_HOST'] = 'localhost'
+		config['REDIS_PORT'] = 6379
+		config['REDIS_DB'] = 0
+		self.redis = redis.Redis(
+				host=config['REDIS_HOST'],
+				port=config['REDIS_PORT'],
+				db=config['REDIS_DB'])
+		return config
+
+	def setUpDB(self):
+		self.redis.flushdb();
diff --git a/tests/utils.py b/tests/utils.py
index 3b98e64b9d8ac3dba8968338ccdd450ab3cc2ee2..491794b129fd9cb2282d2e63e1cb8b039039ef3f 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -68,7 +68,7 @@ class UffdTestCase(unittest.TestCase):
 			'MAIL_SKIP_SEND': True,
 			'SELF_SIGNUP': True,
 		}
-
+		config = self.setUpConfig(config)
 		self.app = create_app(config)
 		self.setUpApp()
 		self.client = self.app.test_client()
@@ -92,6 +92,9 @@ class UffdTestCase(unittest.TestCase):
 		self.setUpDB()
 		db.session.commit()
 
+	def setUpConfig(self, config):
+		return config
+
 	def setUpApp(self):
 		pass
 
diff --git a/uffd/__init__.py b/uffd/__init__.py
index ad186dee28e1e6223661add3014988f61c3e02f4..c6ee05372f3d8e179ef093a21c513b9272c3b33b 100644
--- a/uffd/__init__.py
+++ b/uffd/__init__.py
@@ -68,6 +68,7 @@ def create_app(test_config=None): # pylint: disable=too-many-locals,too-many-sta
 
 	app.register_blueprint(csrf_bp)
 
+	models.init_app(app)
 	views.init_app(app)
 	commands.init_app(app)
 
diff --git a/uffd/default_config.cfg b/uffd/default_config.cfg
index 7b01157fb27e75cf264e21413a7f268c6c1f91c7..035afe4c6a070ff868ef95f397f97b7f27290cb6 100644
--- a/uffd/default_config.cfg
+++ b/uffd/default_config.cfg
@@ -41,6 +41,14 @@ MAIL_PASSWORD='*****'
 MAIL_USE_STARTTLS=True
 MAIL_FROM_ADDRESS='foo@bar.com'
 
+# Optional Redis Server, used for rate limits if configured.
+# Rate Limits fail back to in Database if no redis is configured.
+# If you enable this, we depend on python3-redis
+
+#REDIS_HOST='localhost'
+#REDIS_PORT=6379
+#REDIS_DB=0
+
 # Set to a domain name (e.g. "remailer.example.com") to enable remailer.
 # Requires special mail server configuration (see uffd-socketmapd). Can be
 # enabled/disabled per-service in the service settings. If enabled, services
diff --git a/uffd/models/__init__.py b/uffd/models/__init__.py
index 52d9709b285d4fcacaa837bae570e4af7c79d729..9aa5d3406261d163a7855bb9b92150855b592858 100644
--- a/uffd/models/__init__.py
+++ b/uffd/models/__init__.py
@@ -26,3 +26,6 @@ __all__ = [
 	'User', 'UserEmail', 'Group',
 	'RatelimitEvent', 'Ratelimit', 'HostRatelimit', 'host_ratelimit', 'format_delay',
 ]
+
+def init_app(app):
+	Ratelimit.init_app(app)
diff --git a/uffd/models/ratelimit.py b/uffd/models/ratelimit.py
index cd370956b0e9fa980ff09e5e4d5faca614c83a52..fb9b9dca92d58b46c80cec65af52ef34253853c3 100644
--- a/uffd/models/ratelimit.py
+++ b/uffd/models/ratelimit.py
@@ -24,23 +24,36 @@ class RatelimitEvent(db.Model):
 		return self.expires < datetime.datetime.utcnow()
 
 class Ratelimit:
+	_redis = False
+
 	def __init__(self, name, interval, limit):
 		self.name = name
 		self.interval = interval
 		self.limit = limit
 		self.base = interval**(1/limit)
 
+	@classmethod
+	def init_app(cls, app):
+		if not app.config.get('REDIS_HOST'):
+			cls._redis = False
+		else:
+			import redis
+			cls._redis = redis.Redis(host=app.config['REDIS_HOST'], port=app.config['REDIS_PORT'], db=app.config['REDIS_DB'])
+
+
+	def __redis_get_index(self, key=None):
+		return 'ratelimit:{}{}'.format(self.name, (':' + key) or '')
+
 	def log(self, key=None):
-		db.session.add(RatelimitEvent(name=self.name, key=key, expires=datetime.datetime.utcnow() + datetime.timedelta(seconds=self.interval)))
-		db.session.commit()
+		if not self._redis:
+			db.session.add(RatelimitEvent(name=self.name, key=key, expires=datetime.datetime.utcnow() + datetime.timedelta(seconds=self.interval)))
+			db.session.commit()
+		else:
+			self._redis.incr(self.__redis_get_index(key))
+			self._redis.expire(self.__redis_get_index(key), ttl=self.intervall, nx=True)
 
-	def get_delay(self, key=None):
-		events = RatelimitEvent.query\
-				.filter(db.not_(RatelimitEvent.expired))\
-				.filter_by(name=self.name, key=key)\
-				.order_by(RatelimitEvent.timestamp)\
-				.all()
-		if not events:
+	def get_delay_backoff(self, events):
+		if events < 1:
 			return 0
 		delay = math.ceil(self.base**len(events))
 		if delay < 5:
@@ -49,6 +62,18 @@ class Ratelimit:
 		remaining = events[0].timestamp + datetime.timedelta(seconds=delay) - datetime.datetime.utcnow()
 		return max(0, math.ceil(remaining.total_seconds()))
 
+	def get_delay(self, key=None):
+		if not self._redis:
+			events = RatelimitEvent.query\
+				.filter(db.not_(RatelimitEvent.expired))\
+				.filter_by(name=self.name, key=key)\
+				.order_by(RatelimitEvent.timestamp)\
+				.all()
+		else:
+			events = self._redis.get(self.__redis_get_index(key)) or 0
+
+		return self.get_delay_backoff(len(events))
+
 def get_addrkey(addr=None):
 	if addr is None:
 		addr = request.remote_addr