diff --git a/README.md b/README.md
index e6661c0c606f970aa6ea31c139883c09e05de637..ff9a71490304b76ded5b84d5f9ff011e7a894400 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,13 @@ hook-pre-app = exec:FLASK_APP=uffd flask db upgrade
 
 tabs.
 
+## Config
+
+Uffd reads its default config from `uffd/default_config.cfg`.
+You can overwrite config variables by creating a config file in the `instance` folder.
+The file must be named `conifg.cfg` (Python syntax), `config.json` or `config.yml`/`config.yaml`.
+You can also set a custom file name with the environment variable `CONFIG_FILENAME`.
+
 ## Bind with service account or as user?
 
 Uffd can use a dedicated service account for LDAP operations by setting `LDAP_SERVICE_BIND_DN`.
@@ -70,7 +77,6 @@ Or set `LDAP_SERVICE_USER_BIND` to use the credentials of the currently logged i
 If you choose to run with user credentials, some features are not available, like password resets
 or self signup, since in both cases, no user credentials can exist. 
 
-
 ## OAuth2 Single-Sign-On Provider
 
 Other services can use uffd as an OAuth2.0-based authentication provider.
diff --git a/uffd/__init__.py b/uffd/__init__.py
index 0ea45a235ca02783d804563e7add1dc6306f71c1..05ace003ef3fd8d7fffc2baf134aa264b4e616ac 100644
--- a/uffd/__init__.py
+++ b/uffd/__init__.py
@@ -18,6 +18,24 @@ from uffd.template_helper import register_template_helper
 from uffd.navbar import setup_navbar
 # pylint: enable=wrong-import-position
 
+def load_config_file(app, cfg_name, silent=False):
+	cfg_path = os.path.join(app.instance_path, cfg_name)
+	if not os.path.exists(cfg_path):
+		if not silent:
+			raise Exception(f"Config file {cfg_path} not found")
+		return False
+
+	if cfg_path.endswith(".json"):
+		app.config.from_json(cfg_path)
+	elif cfg_path.endswith(".yaml") or cfg_path.endswith(".yml"):
+		import yaml  # pylint: disable=import-outside-toplevel disable=import-error
+		with open(cfg_path, encoding='utf-8') as ymlfile:
+			data = yaml.safe_load(ymlfile)
+		app.config.from_mapping(data)
+	else:
+		app.config.from_pyfile(cfg_path, silent=True)
+	return True
+
 def create_app(test_config=None): # pylint: disable=too-many-locals
 	# create and configure the app
 	app = Flask(__name__, instance_relative_config=False)
@@ -30,16 +48,19 @@ def create_app(test_config=None): # pylint: disable=too-many-locals
 	)
 	app.config.from_pyfile('default_config.cfg')
 
+	# load config
+	if test_config is not None:
+		app.config.from_mapping(test_config)
+	elif os.environ.get("CONFIG_FILENAME"):
+		load_config_file(app, os.environ["CONFIG_FILENAME"], silent=False)
+	else:
+		for cfg_name in ["config.cfg", "config.json", "config.yml", "config.yaml"]:
+			if load_config_file(app, cfg_name, silent=True):
+				break
+
 	register_template_helper(app)
 	setup_navbar(app)
 
-	if not test_config:
-		# load the instance config, if it exists, when not testing
-		app.config.from_pyfile(os.path.join(app.instance_path, 'config.cfg'), silent=True)
-	else:
-		# load the test config if passed in
-		app.config.from_mapping(test_config)
-
 	# ensure the instance folder exists
 	try:
 		os.makedirs(app.instance_path)