diff --git a/config/config.php b/config/config.php
index bdda1a02..a252a556 100755
--- a/config/config.php
+++ b/config/config.php
@@ -7,6 +7,7 @@ define('RASPI_CONFIG_PROVIDERS', 'config/vpn-providers.json');
 define('RASPI_ADMIN_DETAILS', RASPI_CONFIG.'/raspap.auth');
 define('RASPI_WIFI_AP_INTERFACE', 'wlan0');
 define('RASPI_CACHE_PATH', sys_get_temp_dir() . '/raspap');
+define('RASPI_ERROR_LOG', sys_get_temp_dir() . '/raspap_error.log');
 define('RASPI_DEBUG_LOG', 'raspap_debug.log');
 define('RASPI_LOG_SIZE_LIMIT', 64);
 
diff --git a/includes/defaults.php b/includes/defaults.php
index d7b2972e..317b838b 100755
--- a/includes/defaults.php
+++ b/includes/defaults.php
@@ -12,6 +12,7 @@ $defaults = [
   'RASPI_ADMIN_DETAILS' => RASPI_CONFIG.'/raspap.auth',
   'RASPI_WIFI_AP_INTERFACE' => 'wlan0',
   'RASPI_CACHE_PATH' => sys_get_temp_dir() . '/raspap',
+  'RASPI_ERROR_LOG' => sys_get_temp_dir() . '/raspap_error.log',
   'RASPI_DEBUG_LOG' => 'raspap_debug.log',
   'RASPI_LOG_SIZE_LIMIT' =>  64,
 
diff --git a/includes/exceptions.php b/includes/exceptions.php
new file mode 100755
index 00000000..c52c04ed
--- /dev/null
+++ b/includes/exceptions.php
@@ -0,0 +1,6 @@
+
+
diff --git a/index.php b/index.php
index 9b755744..1137c3a3 100755
--- a/index.php
+++ b/index.php
@@ -26,6 +26,7 @@
 require 'includes/csrf.php';
 ensureCSRFSessionToken();
 
+require_once 'includes/exceptions.php';
 require_once 'includes/config.php';
 require_once 'includes/autoload.php';
 require_once 'includes/defaults.php';
diff --git a/locale/en_US/LC_MESSAGES/messages.mo b/locale/en_US/LC_MESSAGES/messages.mo
index 0b0865ab..d0cd5812 100644
Binary files a/locale/en_US/LC_MESSAGES/messages.mo and b/locale/en_US/LC_MESSAGES/messages.mo differ
diff --git a/locale/en_US/LC_MESSAGES/messages.po b/locale/en_US/LC_MESSAGES/messages.po
index 7053ad78..3c403635 100644
--- a/locale/en_US/LC_MESSAGES/messages.po
+++ b/locale/en_US/LC_MESSAGES/messages.po
@@ -1520,3 +1520,11 @@ msgstr "Update complete"
 msgid "An error occurred. Check the log at /tmp/raspap_install.log"
 msgstr "An error occurred. Check the log at /tmp/raspap_install.log"
 
+#: includes/exceptions.php
+
+msgid "RaspAP Exception"
+msgstr "RaspAP Exception"
+
+msgid "An exception occurred"
+msgstr "An exception occurred"
+
diff --git a/src/RaspAP/Exceptions/ExceptionHandler.php b/src/RaspAP/Exceptions/ExceptionHandler.php
new file mode 100755
index 00000000..d9cf74e9
--- /dev/null
+++ b/src/RaspAP/Exceptions/ExceptionHandler.php
@@ -0,0 +1,69 @@
+
+ * @license     https://github.com/raspap/raspap-webgui/blob/master/LICENSE
+ * @see
+ */
+
+declare(strict_types=1);
+
+namespace RaspAP\Exceptions;
+
+use RaspAP\Exceptions\HtmlErrorRenderer;
+
+class ExceptionHandler
+{
+
+    public function __construct()
+    {
+        $this->setExceptionHandler();
+        $this->setShutdownHandler();
+    }
+
+    public static function handleException($exception)
+    {
+        $errorMessage = (
+            '[' . date('Y-m-d H:i:s') . '] ' .
+            $exception->getMessage() . ' in ' .
+            $exception->getFile() . ' on line ' .
+            $exception->getLine() . PHP_EOL
+        );
+        // Log the exception
+        error_log($errorMessage, 3, RASPI_ERROR_LOG);
+
+        $renderer = new HtmlErrorRenderer();
+        $renderer->render($exception);
+    }
+
+    public static function handleShutdown()
+    {
+        $error = error_get_last();
+        if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
+            $errorMessage = (
+                '[' . date('Y-m-d H:i:s') . '] ' .
+                $error['message'] . ' in ' .
+                $error['file'] . ' on line ' .
+                $error['line'] . PHP_EOL
+            );
+            error_log($errorMessage, 3, RASPI_ERROR_LOG);
+
+            $renderer = new HtmlErrorRenderer();
+            $renderer->render($exception);
+        }
+    }
+
+    protected function setExceptionHandler()
+    {
+        set_exception_handler(array($this, 'handleException'));
+    }
+
+    protected function setShutdownHandler()
+    {
+        register_shutdown_function(array($this, 'handleShutdown'));
+    }
+}
+
diff --git a/src/RaspAP/Exceptions/HtmlErrorRenderer.php b/src/RaspAP/Exceptions/HtmlErrorRenderer.php
new file mode 100755
index 00000000..058a2478
--- /dev/null
+++ b/src/RaspAP/Exceptions/HtmlErrorRenderer.php
@@ -0,0 +1,106 @@
+
+ * @license     https://github.com/raspap/raspap-webgui/blob/master/LICENSE
+ * @see
+ */
+
+declare(strict_types=1);
+
+namespace RaspAP\Exceptions;
+
+class HtmlErrorRenderer
+{
+
+    public function __construct()
+    {
+        $this->charset = 'UTF-8';
+        $this->projectDir = $_SERVER['DOCUMENT_ROOT'];
+        $this->template = '/templates/exception.php';
+        $this->debug = true;
+    }
+
+    public function render($exception)
+    {
+        $message = $exception->getMessage();
+        $code = $exception->getCode();
+        $file = $exception->getFile();
+        $line = $exception->getLine();
+        $trace = $this->getExceptionTraceAsString($exception); 
+
+        header('Content-Type: text/html; charset='.$this->charset);
+        if ($this->debug) {
+            header('X-Debug-Exception: '. rawurlencode($message));
+            header('X-Debug-Exception-File: '. rawurlencode($file).':'.$line);
+        }
+        $__template_data = compact(
+            "message",
+            "code",
+            "file",
+            "line",
+            "trace"
+        );
+        if (is_array($__template_data)) {
+            extract($__template_data);
+        } 
+        $file = $this->projectDir . $this->template;
+        ob_start();
+        include $file;
+        echo ob_get_clean();
+    }
+
+    /**
+     * Improved exception trace
+     * @param object $e     : exception
+     * @param array $seen   : passed to recursive calls to accumulate trace lines already seen
+     * @return array of strings, one entry per trace line
+     * @see https://github.com/php/php-src/blob/master/Zend/zend_exceptions.c
+     */
+    public function getExceptionTraceAsString($e, $seen = null) {
+
+        $starter = $seen ? 'Thrown by: ' : '';
+        $result = array();
+        if (!$seen) $seen = array();
+        $trace  = $e->getTrace();
+        $prev   = $e->getPrevious();
+        $result[] = sprintf('%s%s: %s', $starter, get_class($e), $e->getMessage());
+        $file = $e->getFile();
+        $line = $e->getLine();
+
+        while (true) {
+            $current = "$file:$line";
+            if (is_array($seen) && in_array($current, $seen)) {
+                $result[] = sprintf(' ... %d more', count($trace)+1);
+                break;
+            }
+            $result[] = sprintf(' at %s%s%s(%s%s%s)',
+                count($trace) && array_key_exists('class', $trace[0]) ? str_replace('\\', '.', $trace[0]['class']) : '',
+                count($trace) && array_key_exists('class', $trace[0]) && array_key_exists('function', $trace[0]) ? '.' : '',
+                count($trace) && array_key_exists('function', $trace[0]) ? str_replace('\\', '.', $trace[0]['function']) : '(main)',
+                $line === null ? $file : basename($file),
+                $line === null ? '' : ':',
+                $line === null ? '' : $line);
+
+            if (is_array($seen)) {
+                $seen[] = "$file:$line";
+            }
+            if (!count($trace)) {
+                break;
+            }
+            $file = array_key_exists('file', $trace[0]) ? $trace[0]['file'] : 'Unknown Source';
+            $line = array_key_exists('file', $trace[0]) && array_key_exists('line', $trace[0]) && $trace[0]['line'] ? $trace[0]['line'] : null;
+            array_shift($trace);
+        }
+        $result = join(PHP_EOL, $result);
+        if ($prev) {
+            $result  .= PHP_EOL . getExceptionTraceAsString($prev, $seen);
+        }
+        return $result;
+    }
+
+}
+
diff --git a/templates/exception.php b/templates/exception.php
new file mode 100755
index 00000000..5d5c8c49
--- /dev/null
+++ b/templates/exception.php
@@ -0,0 +1,30 @@
+
+
+