diff --git a/src/client.c b/src/client.c
index 67badab..c4dacbd 100644
--- a/src/client.c
+++ b/src/client.c
@@ -54,19 +54,26 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
     struct timespec begin, end;
     long ret;
     int client_keep_alive;
+
     char buf0[1024], buf1[1024];
     char msg_buf[8192], msg_pre_buf_1[4096], msg_pre_buf_2[4096], err_msg[256];
+    char msg_content[1024];
     char buffer[CHUNK_SIZE];
-    err_msg[0] = 0;
     char host[256], *host_ptr, *hdr_connection;
-    host_config *conf = NULL;
-    long content_length = 0;
-    FILE *file = NULL;
+
     msg_buf[0] = 0;
+    err_msg[0] = 0;
+    msg_content[0] = 0;
+
+    host_config *conf = NULL;
+    FILE *file = NULL;
+
+    long content_length = 0;
     int accept_if_modified_since = 0;
     int use_fastcgi = 0;
     int use_rev_proxy = 0;
     int p_len;
+
     fastcgi_conn fcgi_conn = {.socket = 0, .req_id = 0};
     http_status custom_status;
 
@@ -431,6 +438,21 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
                     sprintf(err_msg, "The status code was set to an invalid or unknown value.");
                     goto respond;
                 }
+
+                if (status_code >= 300 && status_code < 600) {
+                    const char *content_type = http_get_header_field(&res.hdr, "Content-Type");
+                    const char *content_length_f = http_get_header_field(&res.hdr, "Content-Length");
+                    const char *content_encoding = http_get_header_field(&res.hdr, "Content-Encoding");
+                    if (content_encoding == NULL && content_type != NULL && content_length_f != NULL &&
+                        strncmp(content_type, "text/html", 9) == 0)
+                    {
+                        long content_len = strtol(content_length_f, NULL, 10);
+                        if (content_len <= sizeof(msg_content)) {
+                            fastcgi_dump(&fcgi_conn, msg_content, content_len);
+                            goto respond;
+                        }
+                    }
+                }
             }
 
             content_length = -1;
@@ -461,17 +483,20 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
         ret = rev_proxy_init(&req, &res, &ctx, conf, client, &custom_status, err_msg);
         use_rev_proxy = (ret == 0);
 
-        if (use_rev_proxy && res.status->code >= 400 && res.status->code < 600) {
+        // Let 300 be formatted by origin server
+        if (use_rev_proxy && res.status->code >= 301 && res.status->code < 600) {
             const char *content_type = http_get_header_field(&res.hdr, "Content-Type");
             const char *content_length_f = http_get_header_field(&res.hdr, "Content-Length");
-            if (content_type != NULL && content_length_f != NULL && strncmp(content_type, "text/html", 9) == 0) {
+            const char *content_encoding = http_get_header_field(&res.hdr, "Content-Encoding");
+            if (content_encoding == NULL && content_type != NULL && content_length_f != NULL &&
+                strncmp(content_type, "text/html", 9) == 0)
+            {
                 long content_len = strtol(content_length_f, NULL, 10);
-                if (content_len <= 1000) {
+                if (content_len <= sizeof(msg_content)) {
                     ctx.status = res.status->code;
-                    ctx.origin = SERVER;
-
+                    ctx.origin = res.status->code >= 400 ? SERVER : NONE;
                     use_rev_proxy = 0;
-                    rev_proxy_void();
+                    rev_proxy_dump(msg_content, content_len);
                 }
             }
         }
@@ -512,7 +537,7 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
         if (http_get_header_field(&res.hdr, "Accept-Ranges") == NULL) {
             http_add_header_field(&res.hdr, "Accept-Ranges", "none");
         }
-        if (!use_fastcgi && file == NULL && ((res.status->code >= 400 && res.status->code < 600) || err_msg[0] != 0)) {
+        if (!use_fastcgi && file == NULL && ((res.status->code >= 300 && res.status->code < 600) || err_msg[0] != 0)) {
             http_remove_header_field(&res.hdr, "Date", HTTP_REMOVE_ALL);
             http_remove_header_field(&res.hdr, "Server", HTTP_REMOVE_ALL);
             http_remove_header_field(&res.hdr, "Cache-Control", HTTP_REMOVE_ALL);
@@ -527,6 +552,14 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
             const http_doc_info *info = http_get_status_info(res.status);
             const http_status_msg *http_msg = http_get_error_msg(res.status);
 
+            if (res.status->code >= 300 && res.status->code < 400 && msg_content[0] == 0) {
+                const char *location = http_get_header_field(&res.hdr, "Location");
+                if (location != NULL) {
+                    snprintf(msg_content, sizeof(msg_content),
+                             "
\n", location);
+                }
+            }
+
             char *rev_proxy_doc = "";
             if (conf->type == CONFIG_TYPE_REVERSE_PROXY) {
                 const http_status *status = http_get_status(ctx.status);
@@ -554,7 +587,7 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
                     http_msg != NULL ? http_msg->msg : "", err_msg[0] != 0 ? err_msg : "");
             content_length = snprintf(msg_buf, sizeof(msg_buf), http_default_document, res.status->code,
                                       res.status->msg, msg_pre_buf_1, info->mode, info->icon, info->color, host,
-                                      rev_proxy_doc);
+                                      rev_proxy_doc, msg_content[0] != 0 ? msg_content : "");
         }
         if (content_length >= 0) {
             sprintf(buf0, "%li", content_length);
diff --git a/src/lib/fastcgi.c b/src/lib/fastcgi.c
index 432f2e2..0c153d9 100644
--- a/src/lib/fastcgi.c
+++ b/src/lib/fastcgi.c
@@ -513,6 +513,72 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
     }
 }
 
+int fastcgi_dump(fastcgi_conn *conn, char *buf, long len) {
+    FCGI_Header header;
+    long ret;
+    char buf0[256];
+    char *content, *ptr = buf;
+    unsigned short req_id, content_len;
+
+    if (conn->out_buf != NULL && conn->out_len > conn->out_off) {
+        ptr += snprintf(ptr, len, "%.*s", conn->out_len - conn->out_off, conn->out_buf + conn->out_off);
+    }
+
+    while (1) {
+        ret = recv(conn->socket, &header, sizeof(header), 0);
+        if (ret < 0) {
+            print(ERR_STR "Unable to receive from FastCGI socket: %s" CLR_STR, strerror(errno));
+            return -1;
+        } else if (ret != sizeof(header)) {
+            print(ERR_STR "Unable to receive from FastCGI socket: received len (%li) != header len (%li)" CLR_STR,
+                  ret, sizeof(header));
+            return -1;
+        }
+
+        req_id = (header.requestIdB1 << 8) | header.requestIdB0;
+        content_len = (header.contentLengthB1 << 8) | header.contentLengthB0;
+        content = malloc(content_len + header.paddingLength);
+
+        long rcv_len = 0;
+        while (rcv_len < content_len + header.paddingLength) {
+            ret = recv(conn->socket, content + rcv_len, content_len + header.paddingLength - rcv_len, 0);
+            if (ret < 0) {
+                print(ERR_STR "Unable to receive from FastCGI socket: %s" CLR_STR, strerror(errno));
+                free(content);
+                return -1;
+            }
+            rcv_len += ret;
+        }
+
+        if (header.type == FCGI_END_REQUEST) {
+            FCGI_EndRequestBody *body = (FCGI_EndRequestBody *) content;
+            int app_status = (body->appStatusB3 << 24) | (body->appStatusB2 << 16) | (body->appStatusB1 << 8) |
+                             body->appStatusB0;
+            if (body->protocolStatus != FCGI_REQUEST_COMPLETE) {
+                print(ERR_STR "FastCGI protocol error: %i" CLR_STR, body->protocolStatus);
+            }
+            if (app_status != 0) {
+                print(ERR_STR "FastCGI app terminated with exit code %i" CLR_STR, app_status);
+            }
+            close(conn->socket);
+            conn->socket = 0;
+            free(content);
+
+            return 0;
+        } else if (header.type == FCGI_STDERR) {
+            // TODO implement Necronda backend error handling
+            if (conn->mode == FASTCGI_PHP) {
+                fastcgi_php_error(conn, content, content_len, buf0);
+            }
+        } else if (header.type == FCGI_STDOUT) {
+            ptr += snprintf(ptr, len - (ptr - buf), "%.*s", content_len, content);
+        } else {
+            print(ERR_STR "Unknown FastCGI type: %i" CLR_STR, header.type);
+        }
+        free(content);
+    }
+}
+
 int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len) {
     unsigned long rcv_len = 0;
     char *buf[16384];
diff --git a/src/lib/fastcgi.h b/src/lib/fastcgi.h
index 11bd6d0..2d7d9ec 100644
--- a/src/lib/fastcgi.h
+++ b/src/lib/fastcgi.h
@@ -49,6 +49,8 @@ int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg);
 
 int fastcgi_send(fastcgi_conn *conn, sock *client, int flags);
 
+int fastcgi_dump(fastcgi_conn *conn, char *buf, long len);
+
 int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len);
 
 #endif //NECRONDA_SERVER_FASTCGI_H
diff --git a/src/lib/http_static.c b/src/lib/http_static.c
index bd433f3..e3931db 100644
--- a/src/lib/http_static.c
+++ b/src/lib/http_static.c
@@ -126,6 +126,7 @@ const char http_default_document[] =
         "\t\tp{text-align:center;font-size:0.875em;}\n"
         "\t\tdiv.footer{color:var(--soft);font-size:0.75em;text-align:center;margin:2em 0 0.5em 0;}\n"
         "\t\tdiv.footer a{color:var(--soft);}\n"
+        "\t\tul,ol{width:fit-content;margin:auto;}\n"
         "\n"
         "\t\tsection.error-ctx{display:flex;padding:0;border:none;}\n"
         "\t\tdiv.box{flex:100%% 1 1;border:1px solid var(--info);color:var(--info);position:relative;padding:1em;box-sizing:border-box;text-align:center;}\n"
@@ -200,6 +201,7 @@ const char http_default_document[] =
         "\t\n"
         "\t\t\n"
         "%3$s"
+        "%9$s"
         "\t\t\t\n"
         "\t\t\n"
         "%8$s"
diff --git a/src/lib/rev_proxy.c b/src/lib/rev_proxy.c
index 05dd579..4d25f54 100644
--- a/src/lib/rev_proxy.c
+++ b/src/lib/rev_proxy.c
@@ -534,8 +534,8 @@ int rev_proxy_send(sock *client, unsigned long len_to_send, int flags) {
     return 0;
 }
 
-int rev_proxy_void() {
-    // FIXME rev_proxy_void
+int rev_proxy_dump(char *buf, long len) {
+    sock_recv(&rev_proxy, buf, len, 0);
     sock_close(&rev_proxy);
     return 0;
 }
diff --git a/src/lib/rev_proxy.h b/src/lib/rev_proxy.h
index 5d14eea..d7d372a 100644
--- a/src/lib/rev_proxy.h
+++ b/src/lib/rev_proxy.h
@@ -33,6 +33,6 @@ int rev_proxy_init(http_req *req, http_res *res, http_status_ctx *ctx, host_conf
 
 int rev_proxy_send(sock *client, unsigned long len_to_send, int flags);
 
-int rev_proxy_void();
+int rev_proxy_dump(char *buf, long len);
 
 #endif //NECRONDA_SERVER_REV_PROXY_H