diff --git a/uffd/__init__.py b/uffd/__init__.py
index e124a3ce1209e0fd10d280aa32fa065a1d2f67e3..cc7926a7b8b43e31e9e48071318088973c984b20 100644
--- a/uffd/__init__.py
+++ b/uffd/__init__.py
@@ -2,7 +2,7 @@ import os
 import secrets
 import sys
 
-from flask import Flask, redirect, url_for
+from flask import Flask, redirect, url_for, request
 from werkzeug.routing import IntegerConverter
 
 sys.path.append('deps/ldapalchemy')
@@ -53,6 +53,11 @@ def create_app(test_config=None): # pylint: disable=too-many-locals
 	def index(): #pylint: disable=unused-variable
 		return redirect(url_for('selfservice.index'))
 
+	@app.teardown_request
+	def close_connection(e):
+		if hasattr(request, "ldap_connection"):
+			request.ldap_connection.unbind()
+
 	return app
 
 def init_db(app):
diff --git a/uffd/ldap.py b/uffd/ldap.py
index 2d7f6f93d8d06f219edda955668d69021fd21aa2..c9aa38388e95169138c34169bec566c57fd38d59 100644
--- a/uffd/ldap.py
+++ b/uffd/ldap.py
@@ -1,10 +1,12 @@
-from flask import current_app, request, abort
+from flask import current_app, request, abort, session
 
 import ldap3
+from ldap3.core.exceptions import LDAPBindError
 
-from ldapalchemy import LDAPMapper, LDAPCommitError # pylint: disable=unused-import
+from ldapalchemy import LDAPMapper, LDAPCommitError  # pylint: disable=unused-import
 from ldapalchemy.model import Query
 
+
 class FlaskQuery(Query):
 	def get_or_404(self, dn):
 		res = self.get(dn)
@@ -18,13 +20,28 @@ class FlaskQuery(Query):
 			abort(404)
 		return res
 
+
+def connect_and_bind_to_ldap(server, bind_dn, bind_pw):
+	# Using auto_bind cannot close the connection, so define the connection with extra steps
+	_connection = ldap3.Connection(server, bind_dn, bind_pw)
+	if _connection.closed:
+		_connection.open(read_server_info=False)
+	if current_app.config["LDAP_SERVICE_USE_STARTTLS"]:
+		_connection.start_tls(read_server_info=False)
+	if not _connection.bind(read_server_info=True):
+		_connection.unbind()
+		return None
+	return _connection
+
+
 class FlaskLDAPMapper(LDAPMapper):
 	def __init__(self):
 		super().__init__()
+
 		class Model(self.Model):
 			query_class = FlaskQuery
 
-		self.Model = Model # pylint: disable=invalid-name
+		self.Model = Model  # pylint: disable=invalid-name
 
 	@property
 	def session(self):
@@ -48,9 +65,19 @@ class FlaskLDAPMapper(LDAPMapper):
 				current_app.ldap_mock.bind()
 			return current_app.ldap_mock
 		server = ldap3.Server(current_app.config["LDAP_SERVICE_URL"], get_info=ldap3.ALL)
-		auto_bind = ldap3.AUTO_BIND_TLS_BEFORE_BIND if current_app.config["LDAP_SERVICE_USE_STARTTLS"] else True
-		request.ldap_connection = ldap3.Connection(server, current_app.config["LDAP_SERVICE_BIND_DN"],
-			current_app.config["LDAP_SERVICE_BIND_PASSWORD"], auto_bind=auto_bind)
+		# If the configured LDAP service bind_dn is empty, connect to LDAP as a user
+		if current_app.config['LDAP_SERVICE_BIND_DN']:
+			bind_dn = current_app.config["LDAP_SERVICE_BIND_DN"]
+			bind_pw = current_app.config["LDAP_SERVICE_BIND_PASSWORD"]
+		else:
+			bind_dn = session['user_dn']
+			bind_pw = session['user_pw']
+
+		request.ldap_connection = connect_and_bind_to_ldap(server, bind_dn, bind_pw)
+		if not request.ldap_connection:
+			raise LDAPBindError
+
 		return request.ldap_connection
 
+
 ldap = FlaskLDAPMapper()
diff --git a/uffd/selfservice/views.py b/uffd/selfservice/views.py
index 40dc49e010832ed5c5d1d1add343842ad6f2eb61..93b5838b6c80cb5c14a15aee4072c9cc61c973f7 100644
--- a/uffd/selfservice/views.py
+++ b/uffd/selfservice/views.py
@@ -4,7 +4,7 @@ import smtplib
 from email.message import EmailMessage
 import email.utils
 
-from flask import Blueprint, render_template, request, url_for, redirect, flash, current_app
+from flask import Blueprint, render_template, request, url_for, redirect, flash, current_app, session
 
 from uffd.navbar import register_navbar
 from uffd.csrf import csrf_protect
@@ -29,6 +29,7 @@ def index():
 @csrf_protect(blueprint=bp)
 @login_required()
 def update():
+	password_changed = False
 	user = get_current_user()
 	if request.values['displayname'] != user.displayname:
 		if user.set_displayname(request.values['displayname']):
@@ -41,12 +42,16 @@ def update():
 		else:
 			if user.set_password(request.values['password1']):
 				flash('Password changed.')
+				password_changed = True
 			else:
 				flash('Password could not be set.')
 	if request.values['mail'] != user.mail:
 		send_mail_verification(user.loginname, request.values['mail'])
 		flash('We sent you an email, please verify your mail address.')
 	ldap.session.commit()
+	# When using a user_connection, update the connection on password-change
+	if password_changed and not current_app.config['LDAP_SERVICE_BIND_DN']:
+		session['user_pw'] = request.values['password1']
 	return redirect(url_for('selfservice.index'))
 
 @bp.route("/passwordreset", methods=(['GET', 'POST']))
diff --git a/uffd/session/views.py b/uffd/session/views.py
index 0331f2f103f2f2bc9c0459164fa911acc4209755..b401563e0099803c06dcfd0bd6beb1625e05bcd0 100644
--- a/uffd/session/views.py
+++ b/uffd/session/views.py
@@ -9,7 +9,7 @@ from ldap3.core.exceptions import LDAPBindError, LDAPPasswordIsMandatoryError
 from ldapalchemy.core import encode_filter
 
 from uffd.user.models import User
-from uffd.ldap import ldap
+from uffd.ldap import ldap, connect_and_bind_to_ldap
 from uffd.ratelimit import Ratelimit, host_ratelimit, format_delay
 
 bp = Blueprint("session", __name__, template_folder='templates', url_prefix='/')
@@ -31,12 +31,23 @@ def login_get_user(loginname, password):
 		except (LDAPBindError, LDAPPasswordIsMandatoryError):
 			return None
 	else:
-		server = ldap3.Server(current_app.config["LDAP_SERVICE_URL"], get_info=ldap3.ALL)
-		auto_bind = ldap3.AUTO_BIND_TLS_BEFORE_BIND if current_app.config["LDAP_SERVICE_USE_STARTTLS"] else True
-		try:
-			conn = ldap3.Connection(server, dn, password, auto_bind=auto_bind)
-		except (LDAPBindError, LDAPPasswordIsMandatoryError):
-			return None
+		# When using a LDAP service connection, try bind with separate user connection
+		if current_app.config['LDAP_SERVICE_BIND_DN']:
+			server = ldap3.Server(current_app.config["LDAP_SERVICE_URL"], get_info=ldap3.ALL)
+			try:
+				conn = connect_and_bind_to_ldap(server, dn, password)
+			except (LDAPBindError, LDAPPasswordIsMandatoryError):
+				return None
+		else:
+			session.clear()
+			session['user_dn'] = dn
+			session['user_pw'] = password
+			try:
+				conn = ldap.get_connection()
+			except (LDAPBindError, LDAPPasswordIsMandatoryError):
+				session.clear()
+				return None
+
 	conn.search(conn.user, encode_filter(current_app.config["LDAP_USER_SEARCH_FILTER"]))
 	if len(conn.entries) != 1:
 		return None
@@ -50,9 +61,11 @@ def logout():
 	session.clear()
 	return resp
 
-def set_session(user, skip_mfa=False):
+def set_session(user, skip_mfa=False, password=''):
 	session.clear()
 	session['user_dn'] = user.dn
+	if password:
+		session['user_pw'] = password
 	session['logintime'] = datetime.datetime.now().timestamp()
 	session['_csrf_token'] = secrets.token_hex(128)
 	if skip_mfa:
@@ -82,7 +95,9 @@ def login():
 	if not user.is_in_group(current_app.config['ACL_SELFSERVICE_GROUP']):
 		flash('You do not have access to this service')
 		return render_template('login.html', ref=request.values.get('ref'))
-	set_session(user)
+	# If the configured LDAP bind_dn is empty, connect to LDAP as a user
+	# Therefore, we save the password for future binds in the session
+	set_session(user, password=password if not current_app.config['LDAP_SERVICE_BIND_DN'] else '')
 	return redirect(url_for('mfa.auth', ref=request.values.get('ref', url_for('index'))))
 
 def get_current_user():