diff --git a/html/index.php b/html/index.php
index 151a88567626b2508ec5babf24836e8f2b0d12e8..efe328bb390ab4a5a4289c914e98161a81fd88b6 100644
--- a/html/index.php
+++ b/html/index.php
@@ -89,16 +89,13 @@ if (!file_exists(CONFIG_DIR.'/'.CONFIG_FILE)) {
 
 /* Check if fusiondirectory.conf (.CONFIG_FILE) is accessible */
 if (!is_readable(CONFIG_DIR.'/'.CONFIG_FILE)) {
-  msg_dialog::display(
-    _('Configuration error'),
-    sprintf(
+  throw new FatalError(
+    htmlescape(sprintf(
       _('FusionDirectory configuration %s/%s is not readable. Please run fusiondirectory-setup --check-config to fix this.'),
       CONFIG_DIR,
       CONFIG_FILE
-    ),
-    FATAL_ERROR_DIALOG
+    ))
   );
-  exit();
 }
 
 /* Parse configuration file */
@@ -115,15 +112,12 @@ $smarty->compile_dir = $config->get_cfg_value('templateCompileDirectory', SPOOL_
 
 /* Check for compile directory */
 if (!(is_dir($smarty->compile_dir) && is_writable($smarty->compile_dir))) {
-  msg_dialog::display(
-    _('Smarty error'),
-    sprintf(
+  throw new FatalError(
+    htmlescape(sprintf(
       _('Directory "%s" specified as compile directory is not accessible!'),
       $smarty->compile_dir
-    ),
-    FATAL_ERROR_DIALOG
+    ))
   );
-  exit();
 }
 
 /* Check for old files in compile directory */
diff --git a/html/main.php b/html/main.php
index 0772eebdbe8cb300771740300a803c908910d7de..07a2c9c1ce7a8d2bc37aaa413e9c6c8df37d7404 100644
--- a/html/main.php
+++ b/html/main.php
@@ -221,12 +221,9 @@ $smarty->assign("sessionLifetime", $config->get_cfg_value("sessionLifetime", 60
 
 /* If there's some post, take a look if everything is there... */
 if (isset($_POST) && count($_POST) && !isset($_POST['php_c_check'])) {
-  msg_dialog::display(
-    _('Configuration Error'),
-    sprintf(_('Fatal error: not all POST variables have been transfered by PHP - please inform your administrator!')),
-    FATAL_ERROR_DIALOG
+  throw new FatalError(
+    htmlescape(_('Fatal error: not all POST variables have been transfered by PHP - please inform your administrator!'))
   );
-  exit();
 }
 
 /* Assign errors to smarty */
diff --git a/html/setup.php b/html/setup.php
index 3d367f81c507f2d0bb1b8a07b5997fc824a30b6b..c405c8582a0b8b1aeaf96619fda0653255ddf462 100644
--- a/html/setup.php
+++ b/html/setup.php
@@ -61,9 +61,12 @@ $smarty->compile_dir = SPOOL_DIR;
 
 /* Check for compile directory */
 if (!(is_dir($smarty->compile_dir) && is_writable($smarty->compile_dir))) {
-  msg_dialog::display(_("Smarty"), sprintf(_("Directory '%s' specified as compile directory is not accessible!"),
-    $smarty->compile_dir), FATAL_ERROR_DIALOG);
-  exit();
+  throw new FatalError(
+    htmlescape(sprintf(
+      _('Directory "%s" specified as compile directory is not accessible!'),
+      $smarty->compile_dir
+    ))
+  );
 }
 
 /* Get posted language */
diff --git a/include/class_config.inc b/include/class_config.inc
index 169c4fcdf7c000252820ebade38551987d19d060..9b992d0319e8f2842f8195e9d60d0b1d52f2e0a5 100644
--- a/include/class_config.inc
+++ b/include/class_config.inc
@@ -143,11 +143,10 @@ class config
     xml_set_element_handler($this->parser, "tag_open", "tag_close");
 
     if (!xml_parse($this->parser, chop($xmldata))) {
-      $msg = sprintf(_("XML error in fusiondirectory.conf: %s at line %d"),
+      $msg = sprintf(_('XML error in fusiondirectory.conf: %s at line %d'),
             xml_error_string(xml_get_error_code($this->parser)),
             xml_get_current_line_number($this->parser));
-      msg_dialog::display(_("Configuration error"), $msg, FATAL_ERROR_DIALOG);
-      exit;
+      throw new FatalError(htmlescape($msg));
     }
     xml_parser_free($this->parser);
   }
@@ -274,13 +273,12 @@ class config
           $cache[$creds] = cred_decrypt($creds, $_SERVER['HTTP_FDKEY']);
           session::set('HTTP_FDKEY_CACHE', $cache);
         } catch (FusionDirectoryException $e) {
-          $msg = sprintf(
-            _('It seems you are trying to decode something which is not encoded : %s<br/>'."\n".
+          $msg = nl2br(htmlescape(sprintf(
+            _('It seems you are trying to decode something which is not encoded : %s'."\n".
               'Please check you are not using a fusiondirectory.secrets file while your passwords are not encrypted.'),
             $e->getMessage()
-          );
-          msg_dialog::display(_('Configuration error'), $msg, FATAL_ERROR_DIALOG);
-          exit;
+          )));
+          throw new FatalError($msg);
         }
       }
       return $cache[$creds];
@@ -317,8 +315,7 @@ class config
 
       /* Check for connection */
       if (is_null($this->ldapLink) || (is_int($this->ldapLink) && $this->ldapLink == 0)) {
-        msg_dialog::display(_("LDAP error"), _("Cannot bind to LDAP. Please contact the system administrator."), FATAL_ERROR_DIALOG);
-        exit();
+        throw new FatalError(htmlescape(_('Cannot bind to LDAP. Please contact the system administrator.')));
       }
 
       /* Move referrals */
@@ -348,8 +345,7 @@ class config
     global $ui;
 
     if (!isset($this->data['LOCATIONS'][$name])) {
-      msg_dialog::display(_('Error'), sprintf(_('Location "%s" could not be found in the configuration file'), $name), FATAL_ERROR_DIALOG);
-      exit;
+      throw new FatalError(htmlescape(sprintf(_('Location "%s" could not be found in the configuration file'), $name)));
     }
     $this->current = $this->data['LOCATIONS'][$name];
 
diff --git a/include/class_ldap.inc b/include/class_ldap.inc
index e9e6c310965641eb84062e18cd00a09e471cf57f..7cce10a97e60f285c54019adceea0e673fca360b 100644
--- a/include/class_ldap.inc
+++ b/include/class_ldap.inc
@@ -1336,6 +1336,7 @@ class LDAP
       }
       $this->cd($dn);
 
+      $operation = LDAP_MOD;
       if (!$modify) {
         $this->cat($srp, $dn);
         if ($this->count($srp)) {
@@ -1354,6 +1355,7 @@ class LDAP
           $ret = $this->modify($data);
         } else {
           /* The destination entry doesn't exists, create it */
+          $operation = LDAP_ADD;
           $ret = $this->add($data);
         }
       } else {
@@ -1363,7 +1365,8 @@ class LDAP
     }
 
     if (!$this->success()) {
-      msg_dialog::display(_('LDAP error'), msgPool::ldaperror($this->get_error(), $dn, '', get_class()), LDAP_ERROR);
+      $error = new SimplePluginLdapError(NULL, $dn, $operation, $this->get_error(), $this->get_errno());
+      $error->display();
     }
 
     return $ret;
diff --git a/include/class_msg_dialog.inc b/include/class_msg_dialog.inc
index a160224eb2ec19d2a3df399aabd70094f1bea1a9..dfb8e155eb31953616a68cab7543aae3971481e2 100644
--- a/include/class_msg_dialog.inc
+++ b/include/class_msg_dialog.inc
@@ -102,19 +102,13 @@ class msg_dialog
     } else {
       $this->a_Trace = [];
     }
-    if ($this->i_Type == FATAL_ERROR_DIALOG) {
-      restore_error_handler();
-      error_reporting(E_ALL);
-      echo $this->renderFatalErrorDialog();
+    if (session::is_set('msg_dialogs')) {
+      $msg_dialogs = session::get('msg_dialogs');
     } else {
-      if (session::is_set('msg_dialogs')) {
-        $msg_dialogs = session::get('msg_dialogs');
-      } else {
-        $msg_dialogs = [];
-      }
-      $msg_dialogs[] = $this;
-      session::set('msg_dialogs', $msg_dialogs);
+      $msg_dialogs = [];
     }
+    $msg_dialogs[] = $this;
+    session::set('msg_dialogs', $msg_dialogs);
   }
 
   /*!
@@ -130,6 +124,10 @@ class msg_dialog
    */
   public static function display ($title, string $message, int $type = INFO_DIALOG, array $trace = [])
   {
+    if ($type === FATAL_ERROR_DIALOG) {
+      /* Deprecated */
+      throw new FatalError($message);
+    }
     if ($title instanceof FusionDirectoryError) {
       static::display(...$title->computeMsgDialogParameters());
       return;
@@ -164,39 +162,6 @@ class msg_dialog
     return $this->i_ID;
   }
 
-  /*!
-   * \brief Run the message dialog
-   */
-  protected function renderFatalErrorDialog ()
-  {
-    global $config;
-
-    $display =
-      '<!DOCTYPE html>
-      <html><head>
-      <title>FusionDirectory startup failed</title>
-      </head><body>';
-    if (isset($config) && is_object($config) &&
-      $config->get_cfg_value('displayerrors') == 'TRUE') {
-      list($trace, ) = html_trace();
-      $display .= $trace;
-    }
-    $display .=
-      '<table style="width:100%; border:2px solid red;">
-        <tr>
-          <td style="vertical-align:top;padding:10px">
-            <img src="geticon.php?context=status&amp;icon=dialog-error&amp;size=32" alt="'.htmlescape(_('Error')).'"/>
-          </td>
-          <td style="width:100%">
-            <b>'.htmlescape($this->s_Title).'</b><br/>
-            '.$this->s_Message.'<br/><br/>
-            '.htmlescape(_('Please fix the above error and reload the page.')).'
-          </td>
-        </tr>
-      </table></body></html>';
-    return $display;
-  }
-
   /*!
    * \brief Check if the message is confirmed by user
    *
diff --git a/include/class_pluglist.inc b/include/class_pluglist.inc
index ceb769027cd50f332dc2b5a5e6f8ebfdbf348f31..78d652011fa213284b7255f06e3d6480f3273fb2 100644
--- a/include/class_pluglist.inc
+++ b/include/class_pluglist.inc
@@ -575,12 +575,13 @@ class pluglist
       } elseif (is_callable([$plugin, 'mainInc'])) {
         $plugin::mainInc();
       } else {
-        msg_dialog::display(
-          _('Plugin'),
-          sprintf(_("Fatal error: Cannot find any plugin definitions for plugin '%s' ('%s' is not a file)!"), $plugin, "$plugin_dir/main.inc"),
-          FATAL_ERROR_DIALOG
+        throw new FatalError(
+          htmlescape(sprintf(
+            _('Fatal error: Cannot find any plugin definitions for plugin "%s" ("%s" is not a file)!'),
+            $plugin,
+            "$plugin_dir/main.inc"
+          ))
         );
-        exit();
       }
       if ($forceCleanup) {
         $cleanup = $remove_lock = FALSE;
diff --git a/include/class_standAlonePage.inc b/include/class_standAlonePage.inc
index b368b077e4a4a647c8589203ca1842d74a55192c..705d96fa8ce27dde55fa07868ae9b335dc41629c 100644
--- a/include/class_standAlonePage.inc
+++ b/include/class_standAlonePage.inc
@@ -120,10 +120,13 @@ class standAlonePage
 
     /* Check if CONFIG_FILE is accessible */
     if (!is_readable(CONFIG_DIR.'/'.CONFIG_FILE)) {
-      msg_dialog::display(_('Fatal error'),
-                          sprintf(_('FusionDirectory configuration %s/%s is not readable. Aborted.'),
-                                  CONFIG_DIR, CONFIG_FILE), FATAL_ERROR_DIALOG);
-      exit();
+      throw new FatalError(
+        htmlescape(sprintf(
+          _('FusionDirectory configuration %s/%s is not readable. Aborted.'),
+          CONFIG_DIR,
+          CONFIG_FILE
+        ))
+      );
     }
 
     /* Parse configuration file */
@@ -143,11 +146,12 @@ class standAlonePage
 
     /* Check for compile directory */
     if (!(is_dir($smarty->compile_dir) && is_writable($smarty->compile_dir))) {
-      msg_dialog::display(_('Configuration error'),
-                          sprintf(_("Directory '%s' specified as compile directory is not accessible!"),
-                                  $smarty->compile_dir),
-                          FATAL_ERROR_DIALOG);
-      exit();
+      throw new FatalError(
+        htmlescape(sprintf(
+          _('Directory "%s" specified as compile directory is not accessible!'),
+          $smarty->compile_dir
+        ))
+      );
     }
 
     /* Check for old files in compile directory */
diff --git a/include/class_userinfo.inc b/include/class_userinfo.inc
index 2f07fe7042f0635801f99db6169b96ea66e65170..682c68c6eb2c064fe33023fdf282e9b1b86c5cc5 100644
--- a/include/class_userinfo.inc
+++ b/include/class_userinfo.inc
@@ -233,14 +233,13 @@ class userinfo
           $targetFilter = templateHandling::parseString($ACLRule['targetfilter'], $this->cachedAttrs, 'ldap_escape_f');
           $ldap->search($targetFilter, ['dn']);
           if ($ldap->hitSizeLimit()) {
-            msg_dialog::display(
-              _('Error'),
-              sprintf(
+            $error = new FusionDirectoryError(
+              htmlescape(sprintf(
                 _('An ACL assignment for the connected user matched more than than the %d objects limit. This user will not have the ACL rights he should.'),
                 $targetFilterLimit
-              ),
-              ERROR_DIALOG
+              ))
             );
+            $error->display();
           }
           $targetDns = [];
           while ($targetAttrs = $ldap->fetch()) {
@@ -1035,10 +1034,7 @@ class userinfo
     /* look through the entire ldap */
     $ldap = $config->get_ldap_link();
     if (!$ldap->success()) {
-      msg_dialog::display(_('LDAP error'),
-          msgPool::ldaperror($ldap->get_error(FALSE), '', LDAP_AUTH),
-          FATAL_ERROR_DIALOG);
-      exit();
+      throw new FatalError(msgPool::ldaperror($ldap->get_error(FALSE), '', LDAP_AUTH));
     }
 
     $allowed_attributes = ['uid','mail'];
@@ -1109,8 +1105,7 @@ class userinfo
     if ($ui === FALSE) {
       throw new LoginFailureException(ldap::invalidCredentialsError());
     } elseif (is_string($ui)) {
-      msg_dialog::display(_('Internal error'), $ui, FATAL_ERROR_DIALOG);
-      exit();
+      throw new LoginFailureException($ui);
     }
 
     /* password check, bind as user with supplied password  */
diff --git a/include/errors/class_FatalError.inc b/include/errors/class_FatalError.inc
new file mode 100644
index 0000000000000000000000000000000000000000..ae4841c20a62a668033f13cdc49dec42975b264d
--- /dev/null
+++ b/include/errors/class_FatalError.inc
@@ -0,0 +1,93 @@
+<?php
+/*
+  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
+
+  Copyright (C) 2019-2020  FusionDirectory
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+/*! \class FatalError
+    \brief Fatal error class. Does not extend FusionDirectoryError.
+*/
+class FatalError extends Error
+{
+  protected $htmlMessage;
+
+  public function __construct (string $htmlMessage = '', int $code = 0, Throwable $previous = NULL)
+  {
+    $this->htmlMessage = $htmlMessage;
+    parent::__construct(htmlunescape(strip_tags($htmlMessage)), $code, $previous);
+  }
+
+  public function getHtmlMessage ()
+  {
+    return $this->htmlMessage;
+  }
+
+  public function toArray (): array
+  {
+    return [
+      'message' => $this->getMessage(),
+      'line'    => $this->getLine(),
+      'file'    => $this->getFile(),
+      'fatal'   => TRUE,
+    ];
+  }
+
+  public function display ()
+  {
+    restore_error_handler();
+    error_reporting(E_ALL);
+    echo $this->renderFatalErrorDialog();
+  }
+
+  /*!
+   * \brief Render fatal error screen
+   */
+  protected function renderFatalErrorDialog ()
+  {
+    global $config;
+
+    $display =
+      '<!DOCTYPE html>
+      <html><head>
+      <title>'.htmlescape(_('FusionDirectory Fatal Error')).'</title>
+      </head><body>';
+
+    $display .=
+      '<table style="width:100%; border:2px solid red;">
+        <tr>
+          <td style="vertical-align:top;padding:10px">
+            <img src="geticon.php?context=status&amp;icon=dialog-error&amp;size=32" alt="'.htmlescape(_('Error')).'"/>
+          </td>
+          <td style="width:100%">
+            <h3>'.htmlescape(_('Fatal Error')).'</h3>
+            '.$this->getHtmlMessage().'
+          </td>
+        </tr>
+      </table>';
+
+    if (isset($config) && is_object($config) &&
+      $config->get_cfg_value('displayerrors') == 'TRUE') {
+      $trace    = FusionDirectoryError::formatTrace($this);
+      $display  .= print_a($trace, TRUE);
+    }
+
+    $display .= '</body></html>';
+
+    return $display;
+  }
+}
diff --git a/include/errors/class_FusionDirectoryError.inc b/include/errors/class_FusionDirectoryError.inc
index 432d690f3fb45e6dbfcfe5fec1df0da6921d9270..9eb1576a90355d9d4923aa0ff3063c89ce52519f 100644
--- a/include/errors/class_FusionDirectoryError.inc
+++ b/include/errors/class_FusionDirectoryError.inc
@@ -47,21 +47,35 @@ class FusionDirectoryError extends Error
 
   public function computeMsgDialogParameters (): array
   {
-    $trace = $this->getTrace();
+    return [_('Error'), $this->htmlMessage, ERROR_DIALOG, static::formatTrace($this)];
+  }
+
+  public function display ()
+  {
+    msg_dialog::display(...$this->computeMsgDialogParameters());
+  }
+
+  public static function formatTrace(Throwable $throwable): array
+  {
+    $trace = $throwable->getTrace();
+
+    foreach ($trace as &$traceStep) {
+      if (isset($traceStep['function']) && isset($traceStep['class']) && isset($traceStep['type'])) {
+        $traceStep['function'] = $traceStep['class'].$traceStep['type'].$traceStep['function'];
+        unset($traceStep['class']);
+        unset($traceStep['type']);
+      }
+    }
+    unset($traceStep);
 
     array_unshift(
       $trace,
       [
-        'file' => $this->getFile(),
-        'line' => $this->getLine(),
+        'file'  => $throwable->getFile(),
+        'line'  => $throwable->getLine(),
       ]
     );
 
-    return [_('Error'), $this->htmlMessage, ERROR_DIALOG, $trace];
-  }
-
-  public function display ()
-  {
-    msg_dialog::display(...$this->computeMsgDialogParameters());
+    return $trace;
   }
 }
diff --git a/include/errors/class_SimplePluginError.inc b/include/errors/class_SimplePluginError.inc
index 57709e9082f0381c061c1e0180fa296acfc2fb9b..ea2eb5854c7639cd2c6f204047202cfa00852b2a 100644
--- a/include/errors/class_SimplePluginError.inc
+++ b/include/errors/class_SimplePluginError.inc
@@ -102,17 +102,7 @@ class SimplePluginError extends FusionDirectoryError
       $html .= '<br/><br/><i>'.htmlescape(sprintf(_('Example: %s'), $example)).'</i> ';
     }
 
-    $trace = $this->getTrace();
-
-    array_unshift(
-      $trace,
-      [
-        'file' => $this->getFile(),
-        'line' => $this->getLine(),
-      ]
-    );
-
-    return [_('Error'), $html, ERROR_DIALOG, $trace];
+    return [_('Error'), $html, ERROR_DIALOG, FusionDirectoryError::formatTrace($this)];
   }
 
   public static function relocate ($origin, FusionDirectoryError $error)
diff --git a/include/errors/class_SimplePluginHookError.inc b/include/errors/class_SimplePluginHookError.inc
index 5941024c0736f9cdd8e2acccc9ed360ddb4b1056..ce119646f8d293606202de5cf9302387188ef896 100644
--- a/include/errors/class_SimplePluginHookError.inc
+++ b/include/errors/class_SimplePluginHookError.inc
@@ -73,16 +73,6 @@ class SimplePluginHookError extends SimplePluginError
       $html .= 'Result: '.$this->htmlMessage."\n";
     }
 
-    $trace = $this->getTrace();
-
-    array_unshift(
-      $trace,
-      [
-        'file' => $this->getFile(),
-        'line' => $this->getLine(),
-      ]
-    );
-
-    return [_('Error'), $html, ERROR_DIALOG, $trace];
+    return [_('Error'), $html, ERROR_DIALOG, FusionDirectoryError::formatTrace($this)];
   }
 }
diff --git a/include/errors/class_SimplePluginLdapError.inc b/include/errors/class_SimplePluginLdapError.inc
index 1c7eb5ced87f8f919880204ca0e473834ae63b91..b2830937a8d72d7187d406e7e5cfb807cee388d3 100644
--- a/include/errors/class_SimplePluginLdapError.inc
+++ b/include/errors/class_SimplePluginLdapError.inc
@@ -88,16 +88,6 @@ class SimplePluginLdapError extends SimplePluginError
 
     $html .= '<br/><br/><i>'.htmlescape(_('Error:')).'</i> '.$this->htmlMessage;
 
-    $trace = $this->getTrace();
-
-    array_unshift(
-      $trace,
-      [
-        'file' => $this->getFile(),
-        'line' => $this->getLine(),
-      ]
-    );
-
-    return [_('LDAP error'), $html, LDAP_ERROR, $trace];
+    return [_('LDAP error'), $html, LDAP_ERROR, FusionDirectoryError::formatTrace($this)];
   }
 }
diff --git a/include/functions.inc b/include/functions.inc
index 8af0c9ab12d81c75181c6b42d1ac7fcd0c67ce65..8d1ccdf09d5c11925ee66ebe50e0ec40395c9bf3 100644
--- a/include/functions.inc
+++ b/include/functions.inc
@@ -358,10 +358,7 @@ function ldap_init ($server, $base, $binddn = '', $pass = '')
 
   /* Sadly we've no proper return values here. Use the error message instead. */
   if (!$ldap->success()) {
-    msg_dialog::display(_('Fatal error'),
-        sprintf(_("FATAL: Error when connecting the LDAP. Server said '%s'."), $ldap->get_error()),
-        FATAL_ERROR_DIALOG);
-    exit();
+    throw new FatalError(htmlescape(sprintf(_('FATAL: Error when connecting the LDAP. Server said "%s".'), $ldap->get_error())));
   }
 
   /* Preset connection base to $base and return to caller */
@@ -1863,10 +1860,13 @@ function load_all_classes ()
       if (is_readable("$BASE_DIR/$path")) {
         require_once("$BASE_DIR/$path");
       } else {
-        msg_dialog::display(_('Fatal error'),
-            sprintf(_("Cannot locate file '%s' - please run '%s' to fix this"),
-              "$BASE_DIR/$path", '<b>fusiondirectory-setup</b>'), FATAL_ERROR_DIALOG);
-        exit;
+        throw new FatalError(
+          sprintf(
+            htmlescape(_('Cannot locate file "%s" - please run "%s" to fix this')),
+            htmlescape("$BASE_DIR/$path"),
+            '<b>fusiondirectory-setup --update-cache</b>'
+          )
+        );
       }
     }
   }
diff --git a/include/login/class_LoginCAS.inc b/include/login/class_LoginCAS.inc
index 8d87d6f017a86fcb54e0d41229a054a138c63c68..790425445cdc311292a9d48fb2ec141b0e1db208 100644
--- a/include/login/class_LoginCAS.inc
+++ b/include/login/class_LoginCAS.inc
@@ -62,26 +62,20 @@ class LoginCAS extends LoginMethod
     $ui = userinfo::getLdapUser(static::$username);
 
     if ($ui === FALSE) {
-      msg_dialog::display(
-        _('Error'),
-        sprintf(
+      throw new FatalError(
+        htmlescape(sprintf(
           _('CAS user "%s" could not be found in the LDAP'),
           static::$username
-        ),
-        FATAL_ERROR_DIALOG
+        ))
       );
-      exit();
     } elseif (is_string($ui)) {
-      msg_dialog::display(
-        _('Error'),
-        sprintf(
+      throw new FatalError(
+        htmlescape(sprintf(
           _('Login with user "%s" triggered error: %s'),
           static::$username,
           $ui
-        ),
-        FATAL_ERROR_DIALOG
+        ))
       );
-      exit();
     }
 
     $ui->loadACL();
@@ -98,14 +92,12 @@ class LoginCAS extends LoginMethod
     } else {
       echo msg_dialog::get_dialogs();
       if (!empty($message)) {
-        msg_dialog::display(
-          _('Error'),
+        throw new FatalError(
           htmlescape(sprintf(
             _('Login with user "%s" triggered error: %s'),
             static::$username,
             $message
-          )),
-          FATAL_ERROR_DIALOG
+          ))
         );
       }
       exit();
diff --git a/include/login/class_LoginHTTPHeader.inc b/include/login/class_LoginHTTPHeader.inc
index 30cdd15f5152931c3ebd53d536cd8c2200bd9ce8..4d414e56e8467fb549c273c9177624ea3818c775 100644
--- a/include/login/class_LoginHTTPHeader.inc
+++ b/include/login/class_LoginHTTPHeader.inc
@@ -44,40 +44,31 @@ class LoginHTTPHeader extends LoginMethod
     static::$username = $_SERVER['HTTP_'.$header];
 
     if (!static::$username) {
-      msg_dialog::display(
-        _('Error'),
-        sprintf(
+      throw new FatalError(
+        htmlescape(sprintf(
           _('No value found in HTTP header "%s"'),
           $header
-        ),
-        FATAL_ERROR_DIALOG
+        ))
       );
-      exit();
     }
 
     $ui = userinfo::getLdapUser(static::$username);
 
     if ($ui === FALSE) {
-      msg_dialog::display(
-        _('Error'),
-        sprintf(
+      throw new FatalError(
+        htmlescape(sprintf(
           _('Header user "%s" could not be found in the LDAP'),
           static::$username
-        ),
-        FATAL_ERROR_DIALOG
+        ))
       );
-      exit();
     } elseif (is_string($ui)) {
-      msg_dialog::display(
-        _('Error'),
-        sprintf(
+      throw new FatalError(
+        htmlescape(sprintf(
           _('Login with user "%s" triggered error: %s'),
           static::$username,
           $ui
-        ),
-        FATAL_ERROR_DIALOG
+        ))
       );
-      exit();
     }
 
     $ui->loadACL();
@@ -94,14 +85,12 @@ class LoginHTTPHeader extends LoginMethod
     } else {
       echo msg_dialog::get_dialogs();
       if (!empty($message)) {
-        msg_dialog::display(
-          _('Error'),
+        throw new FatalError(
           htmlescape(sprintf(
             _('Login with user "%s" triggered error: %s'),
             static::$username,
             $message
-          )),
-          FATAL_ERROR_DIALOG
+          ))
         );
       }
       exit();
diff --git a/include/login/class_LoginMethod.inc b/include/login/class_LoginMethod.inc
index dc71610a1ccbdbd86d6b55e8e2f1ee0d70363cf8..1753e49f19ad78b1e00f25aaf3b1d86eb768ba4b 100644
--- a/include/login/class_LoginMethod.inc
+++ b/include/login/class_LoginMethod.inc
@@ -51,9 +51,9 @@ class LoginMethod
     foreach ($str as $tr) {
       if (!$tr['STATUS']) {
         if ($tr['IS_MUST_HAVE']) {
-          return _('LDAP schema check reported errors:').'<br/><br/><i>'.$tr['MSG'].'</i>';
+          return htmlescape(_('LDAP schema check reported errors:')).'<br/><br/><i>'.htmlescape($tr['MSG']).'</i>';
         } else {
-          msg_dialog::display(_('LDAP schema error'), $tr['MSG'], WARNING_DIALOG);
+          msg_dialog::display(_('LDAP schema error'), htmlescape($tr['MSG']), WARNING_DIALOG);
         }
       }
     }
@@ -61,7 +61,7 @@ class LoginMethod
   }
 
   /*! \brief Check if locking LDAP branch is here or create it */
-  static function checkForLockingBranch ()
+  static function checkForLockingBranch (): bool
   {
     global $config;
     $ldap = $config->get_ldap_link();
@@ -75,6 +75,8 @@ class LoginMethod
         $error->display();
       }
     }
+
+    return TRUE;
   }
 
   /*! \brief Check username for invalid characters and check password is not empty
@@ -173,14 +175,20 @@ class LoginMethod
   /*! \brief Run each step in $steps, stop on errors */
   static function runSteps ($steps)
   {
-    foreach ($steps as $step) {
-      $status = static::$step();
-      if (is_string($status)) {
-        msg_dialog::display(_('LDAP error'), $status, LDAP_ERROR);
-        return FALSE;
-      } elseif ($status === FALSE) {
-        return FALSE;
+    try {
+      foreach ($steps as $step) {
+        $status = static::$step();
+        if (is_string($status)) {
+          /* Deprecated */
+          msg_dialog::display(_('LDAP error'), $status, LDAP_ERROR);
+          return FALSE;
+        } elseif ($status === FALSE) {
+          return FALSE;
+        }
       }
+    } catch (FusionDirectoryError $e) {
+      $e->display();
+      return FALSE;
     }
     return TRUE;
   }
diff --git a/include/php_setup.inc b/include/php_setup.inc
index a86652a1160c84e90e26b896b60d7bd70a39d91d..7740b87340ab9a50ef8c0fe85b2deaa795d9bca7 100644
--- a/include/php_setup.inc
+++ b/include/php_setup.inc
@@ -284,6 +284,34 @@ function gosaRaiseError ($errno, $errstr, $errfile, $errline)
   set_error_handler('gosaRaiseError', E_WARNING | E_NOTICE | E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE | E_STRICT);
 }
 
+/*!
+ * \brief Catches throwables that no one catched
+ *
+ * \param Throwable $throwable
+ */
+function fusiondirectoryExceptionHandler (Throwable $throwable)
+{
+  try {
+    logging::log('error', 'fatal', '', [], 'Uncaught '.get_class($throwable).': '.$throwable->getMessage());
+  } catch (Throwable $t) {
+    /* Ignore exceptions/errors here */
+  }
+
+  try {
+    if ($throwable instanceof FatalError) {
+      $throwable->display();
+    } else {
+      $error = new FatalError(htmlescape(sprintf(_('Uncaught %s: %s'), get_class($throwable), $throwable->getMessage())), 0, $throwable);
+      $error->display();
+    }
+  } catch (Throwable $t) {
+    /* Minimal display if exceptions happens when building the pretty one */
+    echo 'Uncaught '.get_class($throwable).': '.$throwable->getMessage();
+  }
+
+  exit(255);
+}
+
 /*!
  * \brief Dummy error handler
  */
@@ -310,6 +338,7 @@ $error_collector        = "";
 $error_collector_mailto = "";
 
 set_error_handler('gosaRaiseError', E_WARNING | E_NOTICE | E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE | E_STRICT);
+set_exception_handler('fusiondirectoryExceptionHandler');
 
 $variables_order = "ES";
 ini_set("track_vars", 1);
diff --git a/include/simpleplugin/class_simplePlugin.inc b/include/simpleplugin/class_simplePlugin.inc
index a7493931dcbf6c288a262fbf96317c44ff4add22..666b8b8cbdbfca38f107b80365e22de1b234c1cc 100644
--- a/include/simpleplugin/class_simplePlugin.inc
+++ b/include/simpleplugin/class_simplePlugin.inc
@@ -470,31 +470,24 @@ class simplePlugin implements SimpleTab
   {
     global $config;
     if (!$this->mainTab) {
-      msg_dialog::display(_('Fatal error'), _('Only main tab can compute dn'), FATAL_ERROR_DIALOG);
-      exit;
+      throw new FatalError(htmlescape(_('Only main tab can compute dn')));
     }
     if (!isset($this->parent) || !($this->parent instanceof simpleTabs)) {
-      msg_dialog::display(
-        _('Fatal error'),
-        sprintf(
+      throw new FatalError(
+        htmlescape(sprintf(
           _('Could not compute dn: no parent tab class for "%s"'),
           get_class($this)
-        ),
-        FATAL_ERROR_DIALOG
+        ))
       );
-      exit;
     }
     $infos = $this->parent->objectInfos();
     if ($infos === FALSE) {
-      msg_dialog::display(
-        _('Fatal error'),
-        sprintf(
+      throw new FatalError(
+        htmlescape(sprintf(
           _('Could not compute dn: could not find objectType infos from tab class "%s"'),
           get_class($this->parent)
-        ),
-        FATAL_ERROR_DIALOG
+        ))
       );
-      exit;
     }
     $attr = $infos['mainAttr'];
     $ou   = $infos['ou'];