diff --git a/aclk/aclk_query.c b/aclk/aclk_query.c
index 0698c2d606..ad3756ccda 100644
--- a/aclk/aclk_query.c
+++ b/aclk/aclk_query.c
@@ -128,7 +128,7 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query)
         ACLK_STATS_UNLOCK;
     }
 
-    w->response.code = web_client_api_request_with_node_selection(localhost, w, path);
+    w->response.code = (short)web_client_api_request_with_node_selection(localhost, w, path);
     web_client_timeout_checkpoint_response_ready(w, &t);
 
     if(buffer_strlen(w->response.data) > ACLK_MAX_WEB_RESPONSE_SIZE) {
diff --git a/libnetdata/buffer/buffer.h b/libnetdata/buffer/buffer.h
index 0021c38ff6..5aaa26a079 100644
--- a/libnetdata/buffer/buffer.h
+++ b/libnetdata/buffer/buffer.h
@@ -242,19 +242,16 @@ static inline void buffer_strncat(BUFFER *wb, const char *txt, size_t len) {
     if(unlikely(!txt || !*txt)) return;
 
     const char *t = txt;
-    while(*t) {
-        buffer_need_bytes(wb, len);
-        char *s = &wb->buffer[wb->len];
-        char *d = s;
-        const char *e = &wb->buffer[wb->len + len];
+    buffer_need_bytes(wb, len + 1);
+    char *s = &wb->buffer[wb->len];
+    char *d = s;
+    const char *e = &wb->buffer[wb->len + len];
 
-        while(*t && d < e)
-            *d++ = *t++;
+    while(*t && d < e)
+        *d++ = *t++;
 
-        wb->len += d - s;
-    }
+    wb->len += d - s;
 
-    buffer_need_bytes(wb, 1);
     wb->buffer[wb->len] = '\0';
 
     buffer_overflow_check(wb);
diff --git a/libnetdata/http/http_defs.h b/libnetdata/http/http_defs.h
index 774ea0b71d..906e3bdfb2 100644
--- a/libnetdata/http/http_defs.h
+++ b/libnetdata/http/http_defs.h
@@ -10,6 +10,7 @@
 #define HTTP_RESP_MOVED_PERM 301
 #define HTTP_RESP_REDIR_TEMP 307
 #define HTTP_RESP_REDIR_PERM 308
+#define HTTP_RESP_HTTPS_UPGRADE 399
 
 // HTTP_CODES 4XX Client Errors
 #define HTTP_RESP_BAD_REQUEST 400
diff --git a/web/rtc/webrtc.c b/web/rtc/webrtc.c
index 33fca0b4cd..b4e05c82c0 100644
--- a/web/rtc/webrtc.c
+++ b/web/rtc/webrtc.c
@@ -304,7 +304,7 @@ static void webrtc_execute_api_request(WEBRTC_DC *chan, const char *request, siz
     web_client_timeout_checkpoint_set(w, 0);
     web_client_decode_path_and_query_string(w, path);
     path = (char *)buffer_tostring(w->url_path_decoded);
-    w->response.code = web_client_api_request_with_node_selection(localhost, w, path);
+    w->response.code = (short)web_client_api_request_with_node_selection(localhost, w, path);
     web_client_timeout_checkpoint_response_ready(w, NULL);
 
     size_t sent_bytes = 0;
diff --git a/web/server/web_client.c b/web/server/web_client.c
index abbdb4bea5..f22492f8d4 100644
--- a/web/server/web_client.c
+++ b/web/server/web_client.c
@@ -18,6 +18,14 @@ inline int web_client_permission_denied(struct web_client *w) {
     return HTTP_RESP_FORBIDDEN;
 }
 
+static inline int bad_request_multiple_dashboard_versions(struct web_client *w) {
+    w->response.data->content_type = CT_TEXT_PLAIN;
+    buffer_flush(w->response.data);
+    buffer_strcat(w->response.data, "Multiple dashboard versions given at the URL.");
+    w->response.code = HTTP_RESP_BAD_REQUEST;
+    return HTTP_RESP_BAD_REQUEST;
+}
+
 static inline int web_client_crock_socket(struct web_client *w __maybe_unused) {
 #ifdef TCP_CORK
     if(likely(web_client_is_corkable(w) && !w->tcp_cork && w->ofd != -1)) {
@@ -140,6 +148,8 @@ static void web_client_reset_allocations(struct web_client *w, bool free_all) {
         w->response.zinitialized = false;
         w->flags &= ~WEB_CLIENT_CHUNKED_TRANSFER;
     }
+
+    web_client_reset_path_flags(w);
 }
 
 void web_client_request_done(struct web_client *w) {
@@ -315,74 +325,152 @@ static inline uint8_t contenttype_for_filename(const char *filename) {
     return CT_APPLICATION_OCTET_STREAM;
 }
 
-static inline int access_to_file_is_not_permitted(struct web_client *w, const char *filename) {
+static int append_slash_to_url_and_redirect(struct web_client *w) {
+    // this function returns a relative redirect
+    // it finds the last path component on the URL and just appends / to it
+    //
+    // So, if the URL is:
+    //
+    //        /path/to/file?query_string
+    //
+    // It adds a Location header like this:
+    //
+    //       Location: file/?query_string\r\n
+    //
+    // The web browser already knows that it is inside /path/to/
+    // so it converts the path to /path/to/file/ and executes the
+    // request again.
+
+    buffer_strcat(w->response.header, "Location: ");
+    const char *b = buffer_tostring(w->url_as_received);
+    const char *q = strchr(b, '?');
+    if(q && q > b) {
+        const char *e = q - 1;
+        while(e > b && *e != '/') e--;
+        if(*e == '/') e++;
+
+        size_t len = q - e;
+        buffer_strncat(w->response.header, e, len);
+        buffer_strncat(w->response.header, "/", 1);
+        buffer_strcat(w->response.header, q);
+    }
+    else {
+        const char *e = &b[buffer_strlen(w->url_as_received) - 1];
+        while(e > b && *e != '/') e--;
+        if(*e == '/') e++;
+
+        buffer_strcat(w->response.header, e);
+        buffer_strncat(w->response.header, "/", 1);
+    }
+
+    buffer_strncat(w->response.header, "\r\n", 2);
+
     w->response.data->content_type = CT_TEXT_HTML;
-    buffer_strcat(w->response.data, "Access to file is not permitted: ");
-    buffer_strcat_htmlescape(w->response.data, filename);
-    return HTTP_RESP_FORBIDDEN;
+    buffer_flush(w->response.data);
+    buffer_strcat(w->response.data,
+                  "<!DOCTYPE html><html>"
+                  "<body onload=\"window.location.href = window.location.origin + window.location.pathname + '/' + window.location.search + window.location.hash\">"
+                  "Redirecting. In case your browser does not support redirection, please click "
+                  "<a onclick=\"window.location.href = window.location.origin + window.location.pathname + '/' + window.location.search + window.location.hash\">here</a>."
+                  "</body></html>");
+    return HTTP_RESP_MOVED_PERM;
 }
 
 // Work around a bug in the CMocka library by removing this function during testing.
 #ifndef REMOVE_MYSENDFILE
 
-static bool find_filename_to_serve(const char *filename, char *dst, size_t dst_len, struct stat *statbuf) {
-    // copy the filename to our src buffer
-    char path[FILENAME_MAX + 1];
-    strncpyz(path, filename, FILENAME_MAX);
+static inline int dashboard_version(struct web_client *w) {
+    if(!web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_WITH_VERSION))
+        return -1;
 
-    bool strip = false;
-    while(1) {
-        if(*path)
-            snprintfz(dst, dst_len, "%s/%s", netdata_configured_web_dir, path);
-        else
-            snprintfz(dst, dst_len, "%s", netdata_configured_web_dir);
+    if(web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_IS_V0))
+        return 0;
+    if(web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_IS_V1))
+        return 1;
+    if(web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_IS_V2))
+        return 2;
 
-        // internal_error(true, "WEBFILE: trying '%s', path '%s'", dst, path);
+    return -1;
+}
 
-        strip = false;
-        if (lstat(dst, statbuf) != 0)
-            strip = true;
+static bool find_filename_to_serve(const char *filename, char *dst, size_t dst_len, struct stat *statbuf, struct web_client *w, bool *is_dir) {
+    int d_version = dashboard_version(w);
+    bool has_extension = web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_HAS_FILE_EXTENSION);
 
-        if (!strip && (statbuf->st_mode & S_IFMT) == S_IFDIR) {
-            // it is a directory
-            // let's see if it has index.html in it
-            if(*path)
-                snprintfz(dst, dst_len, "%s/%s/index.html", netdata_configured_web_dir, path);
-            else
-                snprintfz(dst, dst_len, "%s/index.html", netdata_configured_web_dir);
+    int fallback = 0;
 
-            if (lstat(dst, statbuf) != 0 || (statbuf->st_mode & S_IFMT) == S_IFDIR)
-                strip = true;
+    if(has_extension) {
+        if(d_version == -1)
+            snprintfz(dst, dst_len, "%s/%s", netdata_configured_web_dir, filename);
+        else {
+            // check if the filename or directory exists
+            // fallback to the same path without the dashboard version otherwise
+            snprintfz(dst, dst_len, "%s/v%d/%s", netdata_configured_web_dir, d_version, filename);
+            fallback = 1;
         }
-
-        if(!strip && (statbuf->st_mode & S_IFMT) != S_IFREG)
-            strip = true;
-
-        if(strip) {
-            char *s = path, *e = path;
-            while(*e) e++; // find the terminator
-            if(e > s) e--; // find the last character
-
-            while(e >= s && *e != '/') *e-- = '\0'; // find the previous slash
-            while(e >= s && *e == '/') *e-- = '\0'; // zero the slashes
-
-            if(!*s || e <= s) {
-                snprintfz(dst, dst_len, "%s/index.html", netdata_configured_web_dir);
-                if(lstat(dst, statbuf) != 0)
-                    return false;
-                else
-                    break;
-            }
+    }
+    else if(d_version != -1) {
+        if(filename && *filename) {
+            // check if the filename exists
+            // fallback to /vN/index.html otherwise
+            snprintfz(dst, dst_len, "%s/%s", netdata_configured_web_dir, filename);
+            fallback = 2;
         }
-        else
-            break;
+        else {
+            if(filename && *filename)
+                web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_HAS_TRAILING_SLASH);
+            snprintfz(dst, dst_len, "%s/v%d", netdata_configured_web_dir, d_version);
+        }
+    }
+    else {
+        // check if filename exists
+        // this is needed to serve {filename}/index.html, in case a user puts a html file into a directory
+        // fallback to /index.html otherwise
+        snprintfz(dst, dst_len, "%s/%s", netdata_configured_web_dir, filename);
+        fallback = 3;
+    }
+
+    if (lstat(dst, statbuf) != 0) {
+        if(fallback == 1) {
+            snprintfz(dst, dst_len, "%s/%s", netdata_configured_web_dir, filename);
+            if (lstat(dst, statbuf) != 0)
+                return false;
+        }
+        else if(fallback == 2) {
+            if(filename && *filename)
+                web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_HAS_TRAILING_SLASH);
+            snprintfz(dst, dst_len, "%s/v%d", netdata_configured_web_dir, d_version);
+            if (lstat(dst, statbuf) != 0)
+                return false;
+        }
+        else if(fallback == 3) {
+            if(filename && *filename)
+                web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_HAS_TRAILING_SLASH);
+            snprintfz(dst, dst_len, "%s", netdata_configured_web_dir);
+            if (lstat(dst, statbuf) != 0)
+                return false;
+        }
+        else
+            return false;
+    }
+
+    if((statbuf->st_mode & S_IFMT) == S_IFDIR) {
+        size_t len = strlen(dst);
+        if(len > dst_len - 11)
+            return false;
+
+        strncpyz(&dst[len], "/index.html", dst_len - len);
+
+        if (lstat(dst, statbuf) != 0)
+            return false;
+
+        *is_dir = true;
     }
 
-    // internal_error(true, "WEBFILE: final '%s'", dst);
     return true;
 }
 
-int mysendfile(struct web_client *w, char *filename) {
+static int mysendfile(struct web_client *w, char *filename) {
     debug(D_WEB_CLIENT, "%llu: Looking for file '%s/%s'", w->id, netdata_configured_web_dir, filename);
 
     if(!web_client_can_access_dashboard(w))
@@ -413,15 +501,19 @@ int mysendfile(struct web_client *w, char *filename) {
     }
 
     // find the physical file on disk
+    bool is_dir = false;
     char web_filename[FILENAME_MAX + 1];
     struct stat statbuf;
-    if(!find_filename_to_serve(filename, web_filename, FILENAME_MAX, &statbuf)) {
+    if(!find_filename_to_serve(filename, web_filename, FILENAME_MAX, &statbuf, w, &is_dir)) {
         w->response.data->content_type = CT_TEXT_HTML;
         buffer_strcat(w->response.data, "File does not exist, or is not accessible: ");
         buffer_strcat_htmlescape(w->response.data, web_filename);
         return HTTP_RESP_NOT_FOUND;
     }
 
+    if(is_dir && !web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_HAS_TRAILING_SLASH))
+        return append_slash_to_url_and_redirect(w);
+
     // open the file
     w->ifd = open(web_filename, O_NONBLOCK, O_RDONLY);
     if(w->ifd == -1) {
@@ -822,10 +914,6 @@ static inline char *http_header_parse(struct web_client *w, char *s, int parse_u
             //  web_client_enable_deflate(w, 0);
         }
     }
-    else if(hash == hash_forwarded_proto && !strcasecmp(s, "X-Forwarded-Proto")) {
-        if(strcasestr(v, "https"))
-            w->flags |= WEB_CLIENT_FLAG_PROXY_HTTPS;
-    }
     else if(hash == hash_forwarded_host && !strcasecmp(s, "X-Forwarded-Host")) {
         char buffer[NI_MAXHOST];
         strncpyz(buffer, v, ((size_t)(ve - v) < sizeof(buffer) - 1 ? (size_t)(ve - v) : sizeof(buffer) - 1));
@@ -1082,14 +1170,16 @@ void web_client_build_http_header(struct web_client *w) {
         strftime(edate, sizeof(edate), "%a, %d %b %Y %H:%M:%S %Z", tm);
     }
 
-    if (w->response.code == HTTP_RESP_MOVED_PERM) {
+    if (w->response.code == HTTP_RESP_HTTPS_UPGRADE) {
         buffer_sprintf(w->response.header_output,
                        "HTTP/1.1 %d %s\r\n"
                        "Location: https://%s%s\r\n",
                        w->response.code, code_msg,
                        w->server_host ? w->server_host : "",
                        buffer_tostring(w->url_as_received));
-    }else {
+        w->response.code = HTTP_RESP_MOVED_PERM;
+    }
+    else {
         buffer_sprintf(w->response.header_output,
                        "HTTP/1.1 %d %s\r\n"
                        "Connection: %s\r\n"
@@ -1282,36 +1372,9 @@ static inline int web_client_switch_host(RRDHOST *host, struct web_client *w, ch
         }
 
         if (host) {
-            if(!url) { //no delim found
-                debug(D_WEB_CLIENT, "%llu: URL doesn't end with / generating redirect.", w->id);
-                char *protocol, *url_host;
-                protocol = (
-#ifdef ENABLE_HTTPS
-                        SSL_connection(&w->ssl) ||
-#endif
-                        (w->flags & WEB_CLIENT_FLAG_PROXY_HTTPS)) ? "https" : "http";
-
-                url_host = w->forwarded_host;
-                if(!url_host) {
-                    url_host = w->server_host;
-                    if(!url_host) url_host = "";
-                }
-
-                buffer_sprintf(w->response.header, "Location: %s://%s/%s/%s/%s",
-                               protocol, url_host, nodeid?"node":"host", tok, buffer_tostring(w->url_path_decoded));
-
-                if(buffer_strlen(w->url_query_string_decoded)) {
-                    const char *query_string = buffer_tostring(w->url_query_string_decoded);
-                    if(*query_string) {
-                        if(*query_string != '?')
-                            buffer_fast_strcat(w->response.header, "?", 1);
-                        buffer_strcat(w->response.header, query_string);
-                    }
-                }
-                buffer_fast_strcat(w->response.header, "\r\n", 2);
-                buffer_strcat(w->response.data, "Permanent redirect");
-                return HTTP_RESP_REDIR_PERM;
-            }
+            if(!url)
+                //no delim found
+                return append_slash_to_url_and_redirect(w);
 
             size_t len = strlen(url) + 2;
             char buf[len];
@@ -1374,7 +1437,10 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch
             hash_api = 0,
             hash_netdata_conf = 0,
             hash_host = 0,
-            hash_node = 0;
+            hash_node = 0,
+            hash_v0 = 0,
+            hash_v1 = 0,
+            hash_v2 = 0;
 
 #ifdef NETDATA_INTERNAL_CHECKS
     static uint32_t hash_exit = 0, hash_debug = 0, hash_mirror = 0;
@@ -1385,6 +1451,9 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch
         hash_netdata_conf = simple_hash("netdata.conf");
         hash_host = simple_hash("host");
         hash_node = simple_hash("node");
+        hash_v0 = simple_hash("v0");
+        hash_v1 = simple_hash("v1");
+        hash_v2 = simple_hash("v2");
 #ifdef NETDATA_INTERNAL_CHECKS
         hash_exit = simple_hash("exit");
         hash_debug = simple_hash("debug");
@@ -1394,14 +1463,14 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch
 
     // keep a copy of the decoded path, in case we need to serve it as a filename
     char filename[FILENAME_MAX + 1];
-    strncpyz(filename, buffer_tostring(w->url_path_decoded), FILENAME_MAX);
+    strncpyz(filename, decoded_url_path ? decoded_url_path : "", FILENAME_MAX);
 
     char *tok = strsep_skip_consecutive_separators(&decoded_url_path, "/?");
     if(likely(tok && *tok)) {
         uint32_t hash = simple_hash(tok);
         debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok);
 
-        if(unlikely(hash == hash_api && strcmp(tok, "api") == 0)) {                           // current API
+        if(likely(hash == hash_api && strcmp(tok, "api") == 0)) {                           // current API
             debug(D_WEB_CLIENT_ACCESS, "%llu: API request ...", w->id);
             return check_host_and_call(host, w, decoded_url_path, web_client_api_request);
         }
@@ -1409,6 +1478,24 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch
             debug(D_WEB_CLIENT_ACCESS, "%llu: host switch request ...", w->id);
             return web_client_switch_host(host, w, decoded_url_path, hash == hash_node, web_client_process_url);
         }
+        else if(unlikely(hash == hash_v2 && strcmp(tok, "v2") == 0)) {
+            if(web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_WITH_VERSION))
+                return bad_request_multiple_dashboard_versions(w);
+            web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_IS_V2);
+            return web_client_process_url(host, w, decoded_url_path);
+        }
+        else if(unlikely(hash == hash_v1 && strcmp(tok, "v1") == 0)) {
+            if(web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_WITH_VERSION))
+                return bad_request_multiple_dashboard_versions(w);
+            web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_IS_V1);
+            return web_client_process_url(host, w, decoded_url_path);
+        }
+        else if(unlikely(hash == hash_v0 && strcmp(tok, "v0") == 0)) {
+            if(web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_WITH_VERSION))
+                return bad_request_multiple_dashboard_versions(w);
+            web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_IS_V0);
+            return web_client_process_url(host, w, decoded_url_path);
+        }
         else if(unlikely(hash == hash_netdata_conf && strcmp(tok, "netdata.conf") == 0)) {    // netdata.conf
             if(unlikely(!web_client_can_access_netdataconf(w)))
                 return web_client_permission_denied(w);
@@ -1546,7 +1633,33 @@ void web_client_process_request(struct web_client *w) {
                         break;
                     }
 
-                    w->response.code = web_client_process_url(localhost, w, (char *)buffer_tostring(w->url_path_decoded));
+                    web_client_reset_path_flags(w);
+
+                    // find if the URL path has a filename extension
+                    char path[FILENAME_MAX + 1];
+                    strncpyz(path, buffer_tostring(w->url_path_decoded), FILENAME_MAX);
+                    char *s = path, *e = path;
+
+                    // remove the query string and find the last char
+                    for (; *e ; e++) {
+                        if (*e == '?')
+                            break;
+                    }
+
+                    if(e == s || (*(e - 1) == '/'))
+                        web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_HAS_TRAILING_SLASH);
+
+                    // check if there is a filename extension
+                    while (--e > s) {
+                        if (*e == '/')
+                            break;
+                        if(*e == '.') {
+                            web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_HAS_FILE_EXTENSION);
+                            break;
+                        }
+                    }
+
+                    w->response.code = (short)web_client_process_url(localhost, w, path);
                     break;
             }
             break;
@@ -1585,7 +1698,7 @@ void web_client_process_request(struct web_client *w) {
                           " click <a onclick=\"window.location.href ='https://'+ window.location.hostname + ':' "
                           " + window.location.port + window.location.pathname + window.location.search\">here</a>."
                           "</body></html>");
-            w->response.code = HTTP_RESP_MOVED_PERM;
+            w->response.code = HTTP_RESP_HTTPS_UPGRADE;
             break;
         }
 #endif
@@ -2064,7 +2177,7 @@ void web_client_decode_path_and_query_string(struct web_client *w, const char *p
     }
 }
 
-void web_client_zero(struct web_client *w) {
+void web_client_reuse_from_cache(struct web_client *w) {
     // zero everything about it - but keep the buffers
 
     web_client_reset_allocations(w, false);
diff --git a/web/server/web_client.h b/web/server/web_client.h
index 4c2b06a70e..7a74bffe42 100644
--- a/web/server/web_client.h
+++ b/web/server/web_client.h
@@ -33,29 +33,28 @@ typedef enum {
 } HTTP_VALIDATION;
 
 typedef enum web_client_flags {
-    WEB_CLIENT_FLAG_DEAD = 1 << 1, // if set, this client is dead
-
-    WEB_CLIENT_FLAG_KEEPALIVE = 1 << 2, // if set, the web client will be re-used
-
-    WEB_CLIENT_FLAG_WAIT_RECEIVE = 1 << 3, // if set, we are waiting more input data
-    WEB_CLIENT_FLAG_WAIT_SEND = 1 << 4,    // if set, we have data to send to the client
-
-    WEB_CLIENT_FLAG_DO_NOT_TRACK = 1 << 5,      // if set, we should not set cookies on this client
-    WEB_CLIENT_FLAG_TRACKING_REQUIRED = 1 << 6, // if set, we need to send cookies
-
-    WEB_CLIENT_FLAG_TCP_CLIENT = 1 << 7,  // if set, the client is using a TCP socket
-    WEB_CLIENT_FLAG_UNIX_CLIENT = 1 << 8, // if set, the client is using a UNIX socket
-
-    WEB_CLIENT_FLAG_DONT_CLOSE_SOCKET = 1 << 9, // don't close the socket when cleaning up (static-threaded web server)
-
-    WEB_CLIENT_CHUNKED_TRANSFER = 1 << 10, // chunked transfer (used with zlib compression)
-
-    WEB_CLIENT_FLAG_SSL_WAIT_RECEIVE = 1 << 11, // if set, we are waiting more input data from an ssl conn
-    WEB_CLIENT_FLAG_SSL_WAIT_SEND = 1 << 12,    // if set, we have data to send to the client from an ssl conn
-
-    WEB_CLIENT_FLAG_PROXY_HTTPS = 1 << 13, // if set, the client reaches us via an https proxy
+    WEB_CLIENT_FLAG_DEAD                    = (1 << 1), // if set, this client is dead
+    WEB_CLIENT_FLAG_KEEPALIVE               = (1 << 2), // if set, the web client will be re-used
+    WEB_CLIENT_FLAG_WAIT_RECEIVE            = (1 << 3), // if set, we are waiting more input data
+    WEB_CLIENT_FLAG_WAIT_SEND               = (1 << 4), // if set, we have data to send to the client
+    WEB_CLIENT_FLAG_DO_NOT_TRACK            = (1 << 5), // if set, we should not set cookies on this client
+    WEB_CLIENT_FLAG_TRACKING_REQUIRED       = (1 << 6), // if set, we need to send cookies
+    WEB_CLIENT_FLAG_TCP_CLIENT              = (1 << 7), // if set, the client is using a TCP socket
+    WEB_CLIENT_FLAG_UNIX_CLIENT             = (1 << 8), // if set, the client is using a UNIX socket
+    WEB_CLIENT_FLAG_DONT_CLOSE_SOCKET       = (1 << 9), // don't close the socket when cleaning up (static-threaded web server)
+    WEB_CLIENT_CHUNKED_TRANSFER             = (1 << 10), // chunked transfer (used with zlib compression)
+    WEB_CLIENT_FLAG_SSL_WAIT_RECEIVE        = (1 << 11), // if set, we are waiting more input data from an ssl conn
+    WEB_CLIENT_FLAG_SSL_WAIT_SEND           = (1 << 12), // if set, we have data to send to the client from an ssl conn
+    WEB_CLIENT_FLAG_PATH_IS_V0              = (1 << 13), // v0 dashboard found on the path
+    WEB_CLIENT_FLAG_PATH_IS_V1              = (1 << 14), // v1 dashboard found on the path
+    WEB_CLIENT_FLAG_PATH_IS_V2              = (1 << 15), // v2 dashboard found on the path
+    WEB_CLIENT_FLAG_PATH_HAS_TRAILING_SLASH = (1 << 16), // the path has a trailing hash
+    WEB_CLIENT_FLAG_PATH_HAS_FILE_EXTENSION = (1 << 17), // the path ends with a filename extension
 } WEB_CLIENT_FLAGS;
 
+#define WEB_CLIENT_FLAG_PATH_WITH_VERSION (WEB_CLIENT_FLAG_PATH_IS_V0|WEB_CLIENT_FLAG_PATH_IS_V1|WEB_CLIENT_FLAG_PATH_IS_V2)
+#define web_client_reset_path_flags(w) (w)->flags &= ~(WEB_CLIENT_FLAG_PATH_WITH_VERSION|WEB_CLIENT_FLAG_PATH_HAS_TRAILING_SLASH|WEB_CLIENT_FLAG_PATH_HAS_FILE_EXTENSION)
+
 #define web_client_flag_check(w, flag) ((w)->flags & (flag))
 #define web_client_flag_set(w, flag) (w)->flags |= flag
 #define web_client_flag_clear(w, flag) (w)->flags &= ~flag
@@ -210,12 +209,10 @@ void web_client_request_done(struct web_client *w);
 
 void buffer_data_options2string(BUFFER *wb, uint32_t options);
 
-int mysendfile(struct web_client *w, char *filename);
-
 void web_client_build_http_header(struct web_client *w);
 char *strip_control_characters(char *url);
 
-void web_client_zero(struct web_client *w);
+void web_client_reuse_from_cache(struct web_client *w);
 struct web_client *web_client_create(size_t *statistics_memory_accounting);
 void web_client_free(struct web_client *w);
 
diff --git a/web/server/web_client_cache.c b/web/server/web_client_cache.c
index f14d43396a..5aa3af22e9 100644
--- a/web/server/web_client_cache.c
+++ b/web/server/web_client_cache.c
@@ -93,7 +93,7 @@ struct web_client *web_client_get_from_cache(void) {
         web_clients_cache.avail.count--;
         spinlock_unlock(&web_clients_cache.avail.spinlock);
 
-        web_client_zero(w);
+        web_client_reuse_from_cache(w);
 
         spinlock_lock(&web_clients_cache.used.spinlock);
         web_clients_cache.used.reused++;