diff --git a/src/cache.c b/src/cache.c
index 36ddbd4..94a12e3 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -10,22 +10,123 @@
 
 int magic_init() {
     magic = magic_open(MAGIC_MIME);
-    magic_load(magic, "/usr/share/misc/magic.mgc");
+    if (magic == NULL) {
+        fprintf(stderr, ERR_STR "Unable to open magic cookie: %s" CLR_STR "\n", strerror(errno));
+        return -1;
+    }
+    if (magic_load(magic, MAGIC_FILE) != 0) {
+        fprintf(stderr, ERR_STR "Unable to load magic cookie: %s" CLR_STR "\n", magic_error(magic));
+        return -2;
+    }
     return 0;
 }
 
 int cache_init() {
-    magic_init();
+    if (magic_init() != 0) {
+        return -1;
+    }
 
+    int shm_id = shmget(SHM_KEY, FILE_CACHE_SIZE * sizeof(cache_entry), IPC_CREAT | IPC_EXCL);
+    if (shm_id < 0) {
+        fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
+        return -2;
+    }
+
+    void *shm = shmat(shm_id, NULL, SHM_RDONLY);
+    if (shm == (void *) -1) {
+        fprintf(stderr, ERR_STR "Unable to attach shared memory (ro): %s" CLR_STR "\n", strerror(errno));
+        return -3;
+    }
+    cache = shm;
+
+    void *shm_rw = shmat(shm_id, NULL, 0);
+    if (shm_rw == (void *) -1) {
+        fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
+        return -4;
+    }
+    cache = shm_rw;
+    memset(cache, 0, FILE_CACHE_SIZE * sizeof(cache_entry));
+    // TODO load cache from file
+    shmdt(shm_rw);
+    cache = shm;
+
+    return 0;
+}
+
+int cache_unload() {
+    int shm_id = shmget(SHM_KEY, 0, 0);
+    if (shm_id < 0) {
+        fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
+    } else if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
+        fprintf(stderr, ERR_STR "Unable to configure shared memory: %s" CLR_STR "\n", strerror(errno));
+    }
+    shmdt(cache);
+    return 0;
+}
+
+int cache_update_entry(int entry_num, const char *filename) {
+    void *cache_ro = cache;
+    int shm_id = shmget(SHM_KEY, 0, 0);
+    void *shm_rw = shmat(shm_id, NULL, 0);
+    if (shm_rw == (void *) -1) {
+        print(ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR, strerror(errno));
+        return -1;
+    }
+    cache = shm_rw;
+
+    struct stat statbuf;
+    stat(filename, &statbuf);
+    memcpy(&cache[entry_num].meta.stat, &statbuf, sizeof(statbuf));
+
+    strcpy(cache[entry_num].filename, filename);
+    magic_setflags(magic, MAGIC_MIME_TYPE);
+    strcpy(cache[entry_num].meta.type, magic_file(magic, filename));
+    magic_setflags(magic, MAGIC_MIME_ENCODING);
+    strcpy(cache[entry_num].meta.charset, magic_file(magic, filename));
+    cache[entry_num].is_valid_etag = 0;
+    cache[entry_num].is_updating = 0;
+
+    shmdt(shm_rw);
+    cache = cache_ro;
     return 0;
 }
 
 int uri_cache_init(http_uri *uri) {
     if (uri->filename == NULL) {
-        return -1;
+        return 0;
     }
 
+    int i;
+    for (i = 0; i < FILE_CACHE_SIZE; i++) {
+        if (cache[i].filename[0] != 0 && strncmp(cache[i].filename, uri->filename, cache[i].filename_len) == 0) {
+            uri->meta = &cache[i].meta;
+            if (cache[i].is_updating) {
+                return 0;
+            } else {
+                break;
+            }
+        }
+    }
 
+    if (uri->meta == NULL) {
+        for (i = 0; i < FILE_CACHE_SIZE; i++) {
+            if (cache[i].filename[0] == 0) {
+                if (cache_update_entry(i, uri->filename) != 0) {
+                    return -1;
+                }
+                uri->meta = &cache[i].meta;
+                break;
+            }
+        }
+    } else {
+        struct stat statbuf;
+        stat(uri->filename, &statbuf);
+        if (uri->meta->stat.st_mtimensec != statbuf.st_mtimensec) {
+            if (cache_update_entry(i, uri->filename) != 0) {
+                return -1;
+            }
+        }
+    }
 
     return 0;
 }
diff --git a/src/cache.h b/src/cache.h
index 599dc0c..e68e613 100644
--- a/src/cache.h
+++ b/src/cache.h
@@ -9,24 +9,27 @@
 #define NECRONDA_SERVER_CACHE_H
 
 #include <magic.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
 
 magic_t magic;
 
 typedef struct {
-    char *etag;
-    char *type;
-    char *subtype;
-    char *filename_comp;
+    char etag[64];
+    char type[24];
+    char charset[16];
+    char filename_comp[256];
     struct stat stat;
 } meta_data;
 
 typedef struct {
-    char *filename;
+    char filename[256];
     unsigned short filename_len;
+    unsigned char is_valid_etag:1;
+    unsigned char is_updating:1;
     meta_data meta;
 } cache_entry;
 
-cache_entry cache[FILE_CACHE_SIZE];
-int cache_entries = 0;
+cache_entry *cache;
 
 #endif //NECRONDA_SERVER_CACHE_H
diff --git a/src/client.c b/src/client.c
index a6b57f6..5a2c6c7 100644
--- a/src/client.c
+++ b/src/client.c
@@ -39,11 +39,12 @@ int client_request_handler(sock *client, int req_num) {
     struct timespec begin, end;
     int ret, client_keep_alive, dir_mode;
     char buf0[1024], buf1[1024];
-    char msg_buf[4096], msg_pre_buf[4096];
-    char err_msg[256];
+    char msg_buf[4096], msg_pre_buf[4096], err_msg[256];
+    char buffer[CHUNK_SIZE];
     err_msg[0] = 0;
     char *host, *hdr_connection, *webroot;
     unsigned long content_length = 0;
+    FILE *file = NULL;
 
     http_res res;
     sprintf(res.version, "1.1");
@@ -165,13 +166,27 @@ int client_request_handler(sock *client, int req_num) {
     }
 
     if (uri.is_static) {
-        uri_init_cache(&uri);
+        res.status = http_get_status(200);
         http_add_header_field(&res.hdr, "Allow", "GET, HEAD");
         http_add_header_field(&res.hdr, "Accept-Ranges", "bytes");
         if (strncmp(req.method, "GET", 3) != 0 && strncmp(req.method, "HEAD", 4) != 0) {
             res.status = http_get_status(405);
             goto respond;
         }
+
+        ret = uri_cache_init(&uri);
+        if (ret != 0) {
+            res.status = http_get_status(500);
+            sprintf(err_msg, "Unable to communicate with internal file cache.");
+            goto respond;
+        }
+        sprintf(buf0, "%s, charset=%s", uri.meta->type, uri.meta->charset);
+        http_add_header_field(&res.hdr, "Content-Type", buf0);
+
+        file = fopen(uri.filename, "rb");
+        fseek(file, 0, 2);
+        content_length = ftell(file);
+        fseek(file, 0, 0);
     }
 
     respond:
@@ -185,31 +200,63 @@ int client_request_handler(sock *client, int req_num) {
     if (http_get_header_field(&res.hdr, "Accept-Ranges", HTTP_PRESERVE_UPPER) == NULL) {
         http_add_header_field(&res.hdr, "Accept-Ranges", "none");
     }
-    unsigned long len = 0;
+
     if (res.status->code >= 400 && res.status->code < 600) {
         http_error_msg *http_msg = http_get_error_msg(res.status->code);
         sprintf(msg_pre_buf, http_error_document, res.status->code, res.status->msg,
                 http_msg != NULL ? http_msg->err_msg : "", err_msg[0] != 0 ? err_msg : "");
-        len = sprintf(msg_buf, http_default_document, res.status->code, res.status->msg,
-                      msg_pre_buf, res.status->code >= 300 && res.status->code < 400 ? "info" : "error",
-                      http_error_icon, "#C00000");
-        sprintf(buf0, "%li", len);
-        http_add_header_field(&res.hdr, "Content-Length", buf0);
+        content_length = sprintf(msg_buf, http_default_document, res.status->code, res.status->msg,
+                                 msg_pre_buf, res.status->code >= 300 && res.status->code < 400 ? "info" : "error",
+                                 http_error_icon, "#C00000");
         http_add_header_field(&res.hdr, "Content-Type", "text/html; charset=UTF-8");
-    } else {
-        sprintf(buf0, "%li", content_length);
-        http_add_header_field(&res.hdr, "Content-Length", buf0);
     }
+    sprintf(buf0, "%li", content_length);
+    http_add_header_field(&res.hdr, "Content-Length", buf0);
 
     http_send_response(client, &res);
+    clock_gettime(CLOCK_MONOTONIC, &end);
+    char *location = http_get_header_field(&res.hdr, "Location", HTTP_PRESERVE_UPPER);
+    unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
+    print("%s%03i %s%s%s (%s)%s", http_get_status_color(res.status), res.status->code, res.status->msg,
+          location != NULL ? " -> " : "", location != NULL ? location : "", format_duration(micros, buf0), CLR_STR);
+
     if (strncmp(req.method, "HEAD", 4) != 0) {
+        unsigned long snd_len = 0;
+        unsigned long len = 0;
         if (res.status->code >= 400 && res.status->code < 600) {
-            int snd_len = 0;
-            while (snd_len < len) {
+            while (snd_len < content_length) {
                 if (client->enc) {
-                    ret = SSL_write(client->ssl, msg_buf, (int) (len - snd_len));
+                    ret = SSL_write(client->ssl, msg_buf, (int) (content_length - snd_len));
+                    if (ret <= 0) {
+                        print(ERR_STR "Unable to send: %s" CLR_STR, ssl_get_error(client->ssl, ret));
+                    }
                 } else {
-                    ret = send(client->socket, msg_buf, len - snd_len, 0);
+                    ret = send(client->socket, msg_buf, content_length - snd_len, 0);
+                    if (ret < 0) {
+                        print(ERR_STR "Unable to send: %s" CLR_STR, strerror(errno));
+                    }
+                }
+                if (ret < 0) {
+                    break;
+                }
+                snd_len += ret;
+            }
+        } else if (file != NULL) {
+            while (snd_len < content_length) {
+                len = fread(&buffer, 1, CHUNK_SIZE, file);
+                if (client->enc) {
+                    ret = SSL_write(client->ssl, buffer, (int) len);
+                    if (ret <= 0) {
+                        print(ERR_STR "Unable to send: %s" CLR_STR, ssl_get_error(client->ssl, ret));
+                    }
+                } else {
+                    ret = send(client->socket, buffer, len, 0);
+                    if (ret < 0) {
+                        print(ERR_STR "Unable to send: %s" CLR_STR, strerror(errno));
+                    }
+                }
+                if (ret <= 0) {
+                    break;
                 }
                 snd_len += ret;
             }
@@ -217,10 +264,8 @@ int client_request_handler(sock *client, int req_num) {
     }
 
     clock_gettime(CLOCK_MONOTONIC, &end);
-    char *location = http_get_header_field(&res.hdr, "Location", HTTP_PRESERVE_UPPER);
-    unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
-    print("%s%03i %s%s%s (%s)%s", http_get_status_color(res.status), res.status->code, res.status->msg,
-          location != NULL ? " -> " : "", location != NULL ? location : "", format_duration(micros, buf0), CLR_STR);
+    micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
+    print("Transfer complete: %s", format_duration(micros, buf0));
 
     uri_free(&uri);
     abort:
diff --git a/src/necronda-server.c b/src/necronda-server.c
index f9c6aee..a9385b9 100644
--- a/src/necronda-server.c
+++ b/src/necronda-server.c
@@ -80,6 +80,7 @@ void destroy() {
     if (kills > 0) {
         fprintf(stderr, ERR_STR "Killed %i child process(es)" CLR_STR "\n", kills);
     }
+    cache_unload();
     exit(2);
 }
 
@@ -137,6 +138,7 @@ void terminate() {
     } else {
         fprintf(stderr, "Goodbye\n");
     }
+    cache_unload();
     exit(0);
 }
 
@@ -239,7 +241,9 @@ int main(int argc, const char *argv[]) {
     signal(SIGINT, terminate);
     signal(SIGTERM, terminate);
 
-    cache_init();
+    if (cache_init() != 0) {
+        return 1;
+    }
     openssl_init();
 
     client.ctx = SSL_CTX_new(TLS_server_method());
diff --git a/src/necronda-server.h b/src/necronda-server.h
index 904a78b..707a99b 100644
--- a/src/necronda-server.h
+++ b/src/necronda-server.h
@@ -33,8 +33,10 @@
 #define REQ_PER_CONNECTION 100
 #define CLIENT_TIMEOUT 3600
 
+#define CHUNK_SIZE 4096
 #define CLIENT_MAX_HEADER_SIZE 8192
 #define FILE_CACHE_SIZE 1024
+#define SHM_KEY 255641
 
 #define ERR_STR "\x1B[1;31m"
 #define CLR_STR "\x1B[0m"
@@ -52,6 +54,8 @@
 #define SERVER_STR "Necronda/" NECRONDA_VERSION
 #define NECRONDA_DEFAULT "www.necronda.net"
 
+#define MAGIC_FILE "/usr/share/file/misc/magic.mgc"
+
 int SOCKETS[NUM_SOCKETS];
 pid_t CHILDREN[MAX_CHILDREN];
 
diff --git a/src/uri.c b/src/uri.c
index 7f73ccb..cc50fe2 100644
--- a/src/uri.c
+++ b/src/uri.c
@@ -36,6 +36,7 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
     uri->query = NULL;
     uri->filename = NULL;
     uri->uri = NULL;
+    uri->meta = NULL;
     uri->is_static = 1;
     uri->is_dir = 0;
     if (uri_str[0] != '/') {
@@ -155,10 +156,6 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
     return 0;
 }
 
-int uri_init_cache(http_uri *uri) {
-    return 0;
-}
-
 void uri_free(http_uri *uri) {
     if (uri->webroot != NULL) free(uri->webroot);
     if (uri->req_path != NULL) free(uri->req_path);
diff --git a/src/uri.h b/src/uri.h
index b7af308..d2969c6 100644
--- a/src/uri.h
+++ b/src/uri.h
@@ -23,9 +23,9 @@ typedef struct {
     char *query;          // "username=test"
     char *filename;       // "/account/index.php"
     char *uri;            // "/account/login?username=test"
-    meta_data meta;
-    unsigned int is_static:1;
-    unsigned int is_dir:1;
+    meta_data *meta;
+    unsigned char is_static:1;
+    unsigned char is_dir:1;
 } http_uri;