diff --git a/config.c b/config.c
index 4ee8dcf..60f9d23 100644
--- a/config.c
+++ b/config.c
@@ -54,12 +54,41 @@ config_load(IniDispatch *disp, void *config_void)
 
     if(disp->type == INI_KEY)
     {
+        if(CONFINI_IS_KEY("core", "server-port"))
+        {
+            strcpy(config->server_port, disp->value);
+            return 0;
+        }
         if(CONFINI_IS_KEY("core", "database"))
         {
             config->database = malloc(sizeof(char) * (strlen(disp->value) + 1));
             strcpy(config->database, disp->value);
             return 0;
         }
+        if(CONFINI_IS_KEY("core", "not-found-file"))
+        {
+            config->not_found_file = malloc(sizeof(char) * (strlen(disp->value) + 1));
+            strcpy(config->not_found_file, disp->value);
+            return 0;
+        }
+        if(CONFINI_IS_KEY("core", "not-found-file-type"))
+        {
+            config->not_found_file_type = malloc(sizeof(char) * (strlen(disp->value) + 1));
+            strcpy(config->not_found_file_type, disp->value);
+            return 0;
+        }
+        if(CONFINI_IS_KEY("core", "not-found-content"))
+        {
+            config->not_found_content = malloc(sizeof(char) * (strlen(disp->value) + 1));
+            strcpy(config->not_found_content, disp->value);
+            return 0;
+        }
+        if(CONFINI_IS_KEY("core", "not-found-content-type"))
+        {
+            config->not_found_content_type = malloc(sizeof(char) * (strlen(disp->value) + 1));
+            strcpy(config->not_found_content_type, disp->value);
+            return 0;
+        }
         if(CONFINI_IS_KEY("core", "log-level"))
         {
             return config_load_log_level(disp, config);
@@ -69,12 +98,6 @@ config_load(IniDispatch *disp, void *config_void)
             config->discovery_port = atoi(disp->value);
             return 0;
         }
-        if(CONFINI_IS_KEY("core", "server-port"))
-        {
-            strcpy(config->server_port, disp->value);
-            return 0;
-        }
     }
     return 0;
 }
-
diff --git a/core.ini b/core.ini
index 8bb6185..4d2e6f8 100644
--- a/core.ini
+++ b/core.ini
@@ -1,7 +1,11 @@
 [core]
 server-port = 5000
+database = core.sqlite
+not-found-file = 404.html
+not-found-file-mime = text/html
+not-found-content = 404 - NOT FOUND
+not-found-content-type = text/plain
 
 : 4421 for dev-env; 4420 for testing-env; 4419 for prod-env; 4422 for testing
 discovery-port = 4421
-database = core.sqlite
 log-level = debug
diff --git a/endpoint.c b/endpoint.c
index f7a0c90..b068ed4 100644
--- a/endpoint.c
+++ b/endpoint.c
@@ -10,13 +10,9 @@ endpoint_func_index(struct mg_connection *nc, struct http_message *hm, endpoint_
     (void)args;
     (void)hm;
     (void)nc;
+    (void)response;
 
-    static const char content[] = "Emgauwa";
-    response->status_code = 200;
-    response->content_type = "text/plain";
-    response->content_length = STRLEN(content);;
-    response->content = content;
-    response->alloced_content = false;
+    response->status_code = 0;
 
     mg_serve_http(nc, hm, global_config.http_server_opts);
 }
@@ -27,15 +23,23 @@ endpoint_func_not_found(struct mg_connection *nc, struct http_message *hm, endpo
     (void)args;
     (void)hm;
     (void)nc;
+    
+    if(access(global_config.not_found_file, R_OK) != -1)
+    {
+        struct mg_str mime_type = mg_mk_str(global_config.not_found_file_type);
+        response->status_code = 0;
+        mg_http_serve_file(nc, hm, global_config.not_found_file, mime_type, mg_mk_str(""));
+    }
+    else
+    {
+        LOG_DEBUG("404 file not found\n");
+        response->status_code = 404;
+        response->content_type = global_config.not_found_content_type;
+        response->content_length = strlen(global_config.not_found_content);
+        response->content = global_config.not_found_content;
+        response->alloced_content = false;
+    }
 
-    static const char content[] = "404 - NOT FOUND";
-    response->status_code = 404;
-    response->content_type = "text/plain";
-    response->content_length = STRLEN(content);;
-    response->content = content;
-    response->alloced_content = false;
-
-    mg_serve_http(nc, hm, global_config.http_server_opts);
 }
 
 void
diff --git a/handlers/connection.c b/handlers/connection.c
index b36e75e..c7e7870 100644
--- a/handlers/connection.c
+++ b/handlers/connection.c
@@ -7,12 +7,31 @@
 #include <router.h>
 #include <handlers.h>
 
-#define STD_HEADERS "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Allow-Methods: *\r\n"
-#define HEADERS_FMT STD_HEADERS "Content-Type: %s"
+#define HEADERS_FMT "Content-Type: %s"
 
 // -2 for "%s" -1 for \0
 #define HEADERS_FMT_LEN (sizeof(HEADERS_FMT) - 3)
 
+static void
+send_response(struct mg_connection *nc, endpoint_response_t *response)
+{
+    if(response->status_code)
+    {
+        char *response_headers = malloc(sizeof(char) * (HEADERS_FMT_LEN + strlen(response->content_type) + 1));
+        sprintf(response_headers, HEADERS_FMT, response->content_type);
+
+        mg_send_head(nc, response->status_code, response->content_length, response_headers);
+        mg_printf(nc, "%s", response->content);
+
+        free(response_headers);
+
+        if(response->alloced_content)
+        {
+            free((char*)response->content);
+        }
+    }
+}
+
 void
 handler_connection(struct mg_connection *nc, int ev, void *p)
 {
@@ -23,63 +42,70 @@ handler_connection(struct mg_connection *nc, int ev, void *p)
 
         endpoint_t *endpoint = router_find_endpoint(hm->uri.p, hm->uri.len, &hm->method);
 
-        if(endpoint)
+        endpoint_response_t response;
+        static const char content[] = "the server did not create a response";
+        endpoint_response_text(&response, 500, content, STRLEN(content));
+
+        if(!endpoint)
         {
-            if(endpoint->func)
+            /* Normalize path - resolve "." and ".." (in-place). */
+            if (!mg_normalize_uri_path(&hm->uri, &hm->uri)) {
+                mg_http_send_error(nc, 400, NULL);
+                return;
+            }
+            char *request_file_org = malloc(sizeof(char) * hm->uri.len);
+            strncpy(request_file_org, hm->uri.p + 1, hm->uri.len);
+            request_file_org[hm->uri.len - 1] = '\0';
+
+            char *request_file = request_file_org;
+            while(request_file[0] == '/')
             {
-                endpoint_response_t response;
+                ++request_file;
+            }
 
-                static const char content[] = "the server did not create a response";
-                endpoint_response_text(&response, 500, content, STRLEN(content));
-
-                endpoint->func(nc, hm, endpoint->args, &response);
-
-                char *response_headers = malloc(sizeof(char) * (HEADERS_FMT_LEN + strlen(response.content_type) + 1));
-                sprintf(response_headers, HEADERS_FMT, response.content_type);
-
-                mg_send_head(nc, response.status_code, response.content_length, response_headers);
-                mg_printf(nc, "%s", response.content);
-
-                free(response_headers);
-
-                if(response.alloced_content)
-                {
-                    free((char*)response.content);
-                }
-
-                for(int i = 0; i < endpoint->args_count; ++i)
-                {
-                    if(endpoint->args[i].type == ENDPOINT_ARG_TYPE_STR)
-                    {
-                        free((char*)endpoint->args[i].value.v_str);
-                    }
-                }
+            LOG_DEBUG("%s\n", request_file);
+            int access_result = access(request_file, R_OK);
+            free(request_file_org);
+            if(access_result != -1)
+            {
+                response.status_code = 0;
+                mg_serve_http(nc, hm, global_config.http_server_opts);
+                return;
             }
             else
             {
-                if(endpoint->method == HTTP_METHOD_OPTIONS)
-                {
-                    char options_header[256]; // TODO make more generic
-                    sprintf(options_header, STD_HEADERS "Allow: OPTIONS%s%s%s%s",
-                            endpoint->options & HTTP_METHOD_GET ? ", GET" : "",
-                            endpoint->options & HTTP_METHOD_POST ? ", POST" : "",
-                            endpoint->options & HTTP_METHOD_PUT ? ", PUT" : "",
-                            endpoint->options & HTTP_METHOD_DELETE ? ", DELETE" : ""
-                    );
-                    mg_send_head(nc, 204, 0, options_header);
-                }
-                else
-                {
-                    mg_send_head(nc, 501, 0, "Content-Type: text/plain");
-                }
+                endpoint = router_get_not_found_endpoint();
             }
         }
-        else
+
+        if(!endpoint->func)
         {
-            mg_send_head(nc, 500, 0, "Content-Type: text/plain");
+            if(endpoint->method == HTTP_METHOD_OPTIONS)
+            {
+                char options_header[256]; // TODO make more generic
+                sprintf(options_header, "Allow: OPTIONS%s%s%s%s",
+                        endpoint->options & HTTP_METHOD_GET ? ", GET" : "",
+                        endpoint->options & HTTP_METHOD_POST ? ", POST" : "",
+                        endpoint->options & HTTP_METHOD_PUT ? ", PUT" : "",
+                        endpoint->options & HTTP_METHOD_DELETE ? ", DELETE" : ""
+                       );
+                mg_send_head(nc, 204, 0, options_header);
+            }
+            else
+            {
+                mg_send_head(nc, 501, 0, "Content-Type: text/plain");
+            }
         }
 
-        //mg_printf(c, "%.*s", (int)hm->message.len, hm->message.p);
-        //mg_printf(c, "%.*s", (int)hm->body.len, hm->body.p);
+        endpoint->func(nc, hm, endpoint->args, &response);
+        send_response(nc, &response);
+
+        for(int i = 0; i < endpoint->args_count; ++i)
+        {
+            if(endpoint->args[i].type == ENDPOINT_ARG_TYPE_STR)
+            {
+                free((char*)endpoint->args[i].value.v_str);
+            }
+        }
     }
 }
diff --git a/include/config.h b/include/config.h
index 53b4772..17d6db5 100644
--- a/include/config.h
+++ b/include/config.h
@@ -30,6 +30,10 @@ typedef struct
     run_type_t run_type;
     char server_port[6];
     uint16_t discovery_port;
+    char *not_found_file;
+    char *not_found_file_type;
+    char *not_found_content;
+    char *not_found_content_type;
     struct mg_serve_http_opts http_server_opts;
 } config_t;
 
diff --git a/include/router.h b/include/router.h
index b52ba89..93bfec9 100644
--- a/include/router.h
+++ b/include/router.h
@@ -15,6 +15,9 @@ typedef enum
     HTTP_METHOD_OPTIONS = (1 << 4)
 } http_method_e;
 
+endpoint_t*
+router_get_not_found_endpoint();
+
 void
 router_init();
 
diff --git a/main.c b/main.c
index f077cf4..f470e0a 100644
--- a/main.c
+++ b/main.c
@@ -51,6 +51,9 @@ main(int argc, const char** argv)
     /******************** LOAD CONFIG ********************/
 
     global_config.file = "core.ini";
+    global_config.not_found_file = "404.html";
+    global_config.not_found_content = "404 - NOT FOUND";
+    global_config.not_found_content_type = "text/plain";
     global_config.log_level = LOG_LEVEL_INFO;
 
     helper_parse_cli(argc, argv, &global_config);
@@ -69,8 +72,10 @@ main(int argc, const char** argv)
 
     fclose(ini_file);
 
+    memset(&global_config.http_server_opts, 0, sizeof(global_config.http_server_opts));
     global_config.http_server_opts.document_root = ".";  // Serve current directory
-    global_config.http_server_opts.enable_directory_listing = "yes";
+    global_config.http_server_opts.enable_directory_listing = "no";
+    global_config.http_server_opts.extra_headers = "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Allow-Methods: *";
 
 
     /******************** SETUP DATABASE ********************/
diff --git a/router.c b/router.c
index bd4734f..a4b477a 100644
--- a/router.c
+++ b/router.c
@@ -10,7 +10,6 @@
 #include <endpoints/api_v1_relays.h>
 
 static endpoint_t endpoints[ROUTER_ENDPOINTS_MAX_COUNT];
-static endpoint_t endpoint_index;
 static endpoint_t endpoint_not_found;
 static int endpoints_registered = 0;
 static const char delimiter[2] = "/";
@@ -41,16 +40,15 @@ get_method_str_for_int(int method_int)
     return mg_mk_str("GET");
 }
 
+endpoint_t*
+router_get_not_found_endpoint()
+{
+    return &endpoint_not_found;
+}
+
 void
 router_init()
 {
-    // add index endpoint
-    endpoint_index.route = NULL;
-    endpoint_index.func = endpoint_func_index;
-    endpoint_index.method = 0;
-    endpoint_index.args_count = 0;
-    endpoint_index.args = NULL;
-
     // add 404 endpoint
     endpoint_not_found.route = NULL;
     endpoint_not_found.func = endpoint_func_not_found;
@@ -87,7 +85,7 @@ router_register_endpoint(const char *route, int method, endpoint_func_f func)
     {
         struct mg_str method_str = get_method_str_for_int(HTTP_METHOD_OPTIONS);
         options_endpoint = router_find_endpoint(route, strlen(route), &method_str);
-        if(options_endpoint == &endpoint_not_found)
+        if(options_endpoint == NULL)
         {
             options_endpoint = router_register_endpoint(route, HTTP_METHOD_OPTIONS, NULL);
         }
@@ -232,7 +230,7 @@ router_find_endpoint(const char *uri_str, size_t uri_len, struct mg_str *method_
     if(!uri_token)
     {
         free(uri);
-        return &endpoint_index;
+        return NULL;
     }
 
     while(uri_token)
@@ -292,15 +290,22 @@ router_find_endpoint(const char *uri_str, size_t uri_len, struct mg_str *method_
         ++route_part;
     }
 
-    endpoint_t *best_endpoint = &endpoint_not_found;
+    int best_rating = 0;
+    endpoint_t *best_endpoint = NULL;
     for(int i = 0; i < endpoints_registered; ++i)
     {
         int rating = endpoints[i].possible_route;
-        if(rating > best_endpoint->possible_route)
+        if(rating > best_rating)
         {
             best_endpoint = &endpoints[i];
+            best_rating = best_endpoint->possible_route;
         }
     }
+    
+    if(best_endpoint == NULL)
+    {
+        return NULL;
+    }
 
     for(int i = 0; i < best_endpoint->args_count; ++i)
     {