diff --git a/config.yml.j2 b/config.yml.j2
index 8052c7bbc3517bf85cf076024562e93e36bf92d9..715f101bbbfb174c465f50f99088bc39b8255332 100644
--- a/config.yml.j2
+++ b/config.yml.j2
@@ -1,4 +1,4 @@
-# Global configuration parameters
+# Configuration of SOCTools user management web GUI
 
 # Hostname of the SOCtools server
 soctoolsproxy: "{{soctoolsproxy}}"
@@ -15,31 +15,31 @@ smtp:
     password: "{{smtp.password}}"
 
 # Path to the SOCtools CA certificate
-ca_cert_file: "../secrets/CA/ca.crt"
+ca_cert_file: "{{playbook_dir}}/secrets/CA/ca.crt"
 
 # Path to "easyrsa" executable and working directory
-easyrsa_bin: "../roles/ca/files/easyrsa/easyrsa"
-easyrsa_ca_dir: "../secrets/CA"
+easyrsa_bin: "{{playbook_dir}}/roles/ca/files/easyrsa/easyrsa"
+easyrsa_ca_dir: "{{playbook_dir}}/secrets/CA"
 
 # File to store tokens allowing users to download certificates
-token_file: "../secrets/cert_access_tokens"
+token_file: "{{playbook_dir}}/secrets/cert_access_tokens"
 
 # Credentials of the special user for account management
 # Cert and key should be in .pem format, unencrypted
 mgmt_user_name: "soctools-user-mgmt"
-mgmt_user_cert_path: "../secrets/CA/issued/soctools-user-mgmt.crt"
-mgmt_user_key_path: "../secrets/CA/private/soctools-user-mgmt.key"
+mgmt_user_cert_path: "{{playbook_dir}}/secrets/CA/issued/soctools-user-mgmt.crt"
+mgmt_user_key_path: "{{playbook_dir}}/secrets/CA/private/soctools-user-mgmt.key"
 
 user_mgmt_base_url: "https://{{soctoolsproxy}}:5443"
 
 keycloak_base_url: "https://{{soctoolsproxy}}:12443"
 keycloak_users_url: "https://{{soctoolsproxy}}:12443/auth/admin/realms/{{openid_realm}}/users"
-keycloak_admin_password: "{{lookup('password', '{{playbook_dir}}/secrets/passwords/keycloak_admin')}}"
+keycloak_admin_password_file: "{{playbook_dir}}/secrets/passwords/keycloak_admin"
 
-misp_api_key: "{{lookup('password', '{{playbook_dir}}/secrets/tokens/misp')}}"
+misp_api_key_file: "{{playbook_dir}}/secrets/tokens/misp"
 
-thehive_api_key: "{{lookup('password', '{{playbook_dir}}/secrets/tokens/thehive_secret_key')}}"
+thehive_api_key_file: "{{playbook_dir}}/secrets/tokens/thehive_secret_key"
 thehive_org_name: "{{org_name}}"
 
-cortex_api_key: "{{lookup('password', '{{playbook_dir}}/secrets/tokens/cortex_secret_key')}}"
+cortex_api_key_file: "{{playbook_dir}}/secrets/tokens/cortex_secret_key"
 cortex_org_name: "{{org_name}}"
diff --git a/cortex.py b/cortex.py
index 947721cbd01958ac794ec6c61115eb167a1dcd79..91bfa7573e7326962023187a9ca0f9a69c9ee9f0 100644
--- a/cortex.py
+++ b/cortex.py
@@ -132,10 +132,14 @@ def cortex_delete_user(login: str) -> None:
 # Auxiliary functions
 
 def _send_request(method:str, url:str, data:Optional[dict]=None):
+    try:
+        api_key = open(config['cortex_api_key_file'], 'r').read(100).strip() # read max 100 B, the key should never be so long
+    except IOError as e:
+        raise IOError(f"Can't load Cortex API key from file: {e}")
     return getattr(requests, method)(
         url,
         headers={
-            "Authorization": "Bearer " + config['cortex_api_key'],
+            "Authorization": "Bearer " + api_key,
         },
         verify=config['ca_cert_file'],
         json=data
diff --git a/main.py b/main.py
index 1cac4824b9d24ed8f8b1b30824cd1080c11b581e..772b4a6cd9befc874de79a25982ff6cbb5d5a430 100644
--- a/main.py
+++ b/main.py
@@ -27,8 +27,9 @@ try:
     # Check presence of all mandatory configuration parameters
     for attr in ('soctoolsproxy', 'ca_cert_file', 'easyrsa_bin', 'easyrsa_ca_dir', 'token_file',
                  'mgmt_user_name', 'mgmt_user_cert_path', 'mgmt_user_key_path', 'user_mgmt_base_url',
-                 'keycloak_base_url', 'keycloak_users_url', 'keycloak_admin_password',
-                 'misp_api_key', 'thehive_api_key', 'thehive_org_name', 'cortex_api_key', 'cortex_org_name',):
+                 'keycloak_base_url', 'keycloak_users_url', 'keycloak_admin_password_file',
+                 'misp_api_key_file', 'thehive_api_key_file', 'thehive_org_name',
+                 'cortex_api_key_file', 'cortex_org_name',):
         if attr not in config or not config[attr]:
             errors.append(f'Missing mandatory parameter "{attr}".')
             config[attr] = None # to avoid attr-access errors in some imported modules
@@ -41,7 +42,7 @@ try:
     if not isinstance(config.get('smtp'), dict):
         smtp_errors.append('Parameter "smtp" is missing or it is not a dict.')
     else:
-        if not re.match('[a-zA-Z0-9.:-]+', config['smtp'].get('host')):
+        if not config['smtp'].get('host') or not re.match('[a-zA-Z0-9.:-]+', config['smtp'].get('host')):
             smtp_errors.append('Parameter "smtp.host" is missing or it is not a valid hostname or IP address.')
         if not config['smtp'].get('sender'):
             smtp_errors.append('Parameter "smtp.sender" is missing.')
@@ -61,10 +62,10 @@ if invalid_configuration_error:
     print("ERROR: " + invalid_configuration_error)
 else:
     print(f"Config loaded:\nsoctoolsproxy={config['soctoolsproxy']}\nkeycloak_base_url={config['keycloak_base_url']}\n"
-          f"keycloak_admin_password={config['keycloak_admin_password'][:3]}...{config['keycloak_admin_password'][-4:]}\n"
-          f"misp_api_key={config['misp_api_key'][:3]}...{config['misp_api_key'][-4:]}\n"
-          f"thehive_api_key={config['thehive_api_key'][:3]}...{config['thehive_api_key'][-4:]}\n"
-          f"cortex_api_key={config['cortex_api_key'][:3]}...{config['cortex_api_key'][-4:]}\n"
+          f"keycloak_admin_password_file={config['keycloak_admin_password_file']}\n"
+          f"misp_api_key_file={config['misp_api_key_file']}\n"
+          f"thehive_api_key_file={config['thehive_api_key_file']}\n"
+          f"cortex_api_key_file={config['cortex_api_key_file']}\n"
           f"thehive_org_name={config['thehive_org_name']}\n")
 
 
@@ -157,11 +158,16 @@ def kc_get_token() -> str:
     
     Return the token or raise KeycloakError
     """
+    try:
+        passwd = open(config['keycloak_admin_password_file'], 'r').read(100).strip() # read max 100 B, the key should never be so long
+    except IOError as e:
+        raise IOError(f"Can't load Keycloak admin password from file: {e}")
+
     url = config['keycloak_base_url'] + "/auth/realms/master/protocol/openid-connect/token"
     data = {
         "client_id": "admin-cli",
         "username": "admin",
-        "password": config['keycloak_admin_password'],
+        "password": passwd,
         "grant_type": "password"
     }
     try:
@@ -303,14 +309,14 @@ class AddUserForm(FlaskForm):
 @app.route("/")
 def main():
     # In case of invalid SMTP configuration, show warning message
-    if invalid_smtp is not None and request.endpoint != "export_certificate":
+    if invalid_smtp is not None:
         flash(invalid_smtp + f" To fix it, edit the configuration file ('{os.path.abspath('config.yml')}') and restart"
                              " the user-mgmt-ui service ('systemctl restart user-mgmt-ui').", "warning")
 
     # Load existing users from Keycloak
     try:
         users = kc_get_users()
-    except KeycloakError as e:
+    except (KeycloakError, IOError) as e:
         flash(f"ERROR: {e}", "error")
         users = []
     # Mark "internal" users
@@ -337,7 +343,7 @@ def main():
     # Load MISP users
     try:
         misp_users = misp_get_users()
-    except MISPError as e:
+    except (MISPError, IOError) as e:
         flash(f"ERROR: {e}", "error")
         misp_users = []
     # Mark "internal" users
@@ -351,7 +357,7 @@ def main():
     # Load The Hive users
     try:
         thehive_users = thehive_get_users()
-    except TheHiveError as e:
+    except (TheHiveError, IOError) as e:
         flash(f"ERROR: {e}", "error")
         thehive_users = []
     # Mark "internal" users
@@ -365,7 +371,7 @@ def main():
     # Load Cortex users
     try:
         cortex_users = cortex_get_users()
-    except CortexError as e:
+    except (CortexError, IOError) as e:
         flash(f"ERROR: {e}", "error")
         cortex_users = []
     # List of usernames only (for easier cross-check with Keycloak users)
@@ -458,7 +464,7 @@ def edit_user(username: str):
     """Edit existing user. On GET show user details, on POST update user params with new values."""
     try:
         user = kc_get_user_by_name(username)
-    except KeycloakError as e:
+    except (KeycloakError, IOError) as e:
         flash(f'ERROR: {e}', "error")
         return redirect_to_main_page()
     keycloak_id = user.kcid
@@ -478,7 +484,7 @@ def edit_user(username: str):
             try:
                 kc_update_user(new_user)
                 flash(f'User "{new_user.username}" successfully updated in Keycloak.', "success")
-            except KeycloakError as e:
+            except (KeycloakError, IOError) as e:
                 flash(f'Error when updating user in Keycloak: {e}', "error")
 
             # NiFi
@@ -543,7 +549,7 @@ def delete_user(username: str):
     """Delete user given by username and redirect back to main page"""
     try:
         user_spec = kc_get_user_by_name(username)
-    except KeycloakError as e:
+    except (KeycloakError, IOError) as e:
         flash(f"Error: Can't get user info from KeyCloak: {e}", "error")
         return redirect_to_main_page()
 
@@ -557,7 +563,7 @@ def delete_user(username: str):
     try:
         kc_delete_user(user_spec.kcid)
         flash(f'User "{user_spec.username}" successfully deleted from KeyCloak.', "success")
-    except KeycloakError as e:
+    except (KeycloakError, IOError) as e:
         flash(f'Error when deleting user from KeyCloak: {e}', "error")
 
     # NiFi
@@ -575,7 +581,7 @@ def delete_user(username: str):
         flash(f'User "{user_spec.email}" successfully deleted from MISP.', "success")
     except MISPUserNotFoundError:
         flash(f'User "{user_spec.email}" was not found in MISP, nothing has changed.', "warning")
-    except MISPError as e:
+    except (MISPError, IOError) as e:
         flash(f'Error when deleting user from MISP: {e}', "error")
 
     # The Hive
@@ -584,7 +590,7 @@ def delete_user(username: str):
         flash(f'User "{user_spec.email}" successfully deleted from The Hive.', "success")
     except TheHiveUserNotFoundError:
         flash(f'Error when deleting user from The Hive: User with email "{user_spec.email}" not found', "error")
-    except TheHiveError as e:
+    except (TheHiveError, IOError) as e:
         flash(f'Error when deleting user from The Hive: {e}', "error")
 
     # Cortex
@@ -593,7 +599,7 @@ def delete_user(username: str):
         flash(f'User "{user_spec.email}" marked as "locked" in Cortex.', "success")
     except CortexUserNotFoundError:
         flash(f'Error when trying to mark user as "locked" in Cortex: User with email "{user_spec.email}" not found', "error")
-    except TheHiveError as e:
+    except (TheHiveError, IOError) as e:
         flash(f'Error when trying to mark user as "locked" in Cortex: {e}', "error")
 
     return redirect_to_main_page()
diff --git a/misp.py b/misp.py
index c5fe5af3c56626c45e206c2b5f2c61c22a77fdea..e9c0198f9dc0881da50e32b2a69bfee52306d464 100644
--- a/misp.py
+++ b/misp.py
@@ -131,10 +131,14 @@ def misp_delete_user(user_email: str) -> None:
 # Auxiliary functions
 
 def _send_request(method:str, url:str, data:Optional[dict]=None):
+    try:
+        api_key = open(config['misp_api_key_file'], 'r').read(100).strip() # read max 100 B, the key should never be so long
+    except IOError as e:
+        raise IOError(f"Can't load MISP API key from file: {e}")
     return getattr(requests, method)(
         url,
         headers={
-            "Authorization": config['misp_api_key'],
+            "Authorization": api_key,
             "Accept": "application/json",
         },
         verify=config['ca_cert_file'],
diff --git a/thehive.py b/thehive.py
index 2f32d2eb91d1ec3c1e5d7a4efab617c38b6d7cfe..9ffc4f1dcccd2715835a4780398eb3a949479457 100644
--- a/thehive.py
+++ b/thehive.py
@@ -135,10 +135,14 @@ def thehive_delete_user(login: str) -> None:
 # Auxiliary functions
 
 def _send_request(method:str, url:str, data:Optional[dict]=None):
+    try:
+        api_key = open(config['thehive_api_key_file'], 'r').read(100).strip() # read max 100 B, the key should never be so long
+    except IOError as e:
+        raise IOError(f"Can't load The Hive API key from file: {e}")
     return getattr(requests, method)(
         url,
         headers={
-            "Authorization": "Bearer " + config['thehive_api_key'],
+            "Authorization": "Bearer " + api_key,
         },
         verify=config['ca_cert_file'],
         json=data