diff --git a/docs/README.md b/docs/README.md
index c1ab71abd18763cc46f6141cbc08961a914f4007..5cf22551ab27d47906e1b1dc121abdc5555f427b 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -603,21 +603,20 @@ Free memory pointed by `io->buf` and set to NULL. Both `size` and `len` are set
 to 0.
 
 
-### mg\_iobuf\_append()
+### mg\_iobuf\_add()
 
 ```c
-size_t mg_iobuf_append(struct mg_iobuf *io, const void *data, size_t data_size, size_t granularity);
+size_t mg_iobuf_add(struct mg_iobuf *io, size_t offset, const void *buf, size_t len, size_t granularity);
 ```
 
-Append `data` bytes of size `data_size` to the end of the buffer. The buffer
-is expanded if `data_size` is greater than `io->size - io->len`. If that
-happens, the `io->buf` can change. The resulting `io->size` is always
-set to the `granularity` byte boundary. Example:
+Insert data buffer `buf`, `len` at offset `offset`. The iobuf gets
+is expanded if required. The resulting `io->size` is always
+aligned to the `granularity` byte boundary. Example:
 
 ```c
 struct mg_iobuf io;
-mg_iobuf_init(&io, 0);                // Empty buffer
-mg_iobuf_append(&io, "hi", 2, 1024);  // io->len is 2, io->size is 1024
+mg_iobuf_init(&io, 0);               // Empty buffer
+mg_iobuf_add(&io, 0, "hi", 2, 512);  // io->len is 2, io->size is 512
 ```
 
 ### mg\_iobuf\_del()
diff --git a/mongoose.c b/mongoose.c
index e3bbc6f08663636e13251e79ae146d042024eca5..ee990d01d8cef3208b49072e650215bc284d8b46 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -1762,8 +1762,8 @@ int mg_iobuf_init(struct mg_iobuf *io, size_t size) {
   return mg_iobuf_resize(io, size);
 }
 
-size_t mg_iobuf_append(struct mg_iobuf *io, const void *buf, size_t len,
-                       size_t chunk_size) {
+size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf,
+                    size_t len, size_t chunk_size) {
   size_t new_size = io->len + len;
   if (new_size > io->size) {
     new_size += chunk_size;             // Make sure that io->size
@@ -1771,7 +1771,9 @@ size_t mg_iobuf_append(struct mg_iobuf *io, const void *buf, size_t len,
     mg_iobuf_resize(io, new_size);      // Attempt to realloc
     if (new_size != io->size) len = 0;  // Realloc failure, append nothing
   }
-  if (buf != NULL) memmove(io->buf + io->len, buf, len);
+  if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs);
+  if (buf != NULL) memmove(io->buf + ofs, buf, len);
+  if (ofs > io->len) io->len += ofs - io->len;
   io->len += len;
   return len;
 }
@@ -2935,7 +2937,7 @@ static long mg_sock_send(struct mg_connection *c, const void *buf, size_t len) {
 
 bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
   return c->is_udp ? mg_sock_send(c, buf, len) > 0
-                   : mg_iobuf_append(&c->send, buf, len, MG_IO_SIZE);
+                   : mg_iobuf_add(&c->send, c->send.len, buf, len, MG_IO_SIZE);
 }
 
 static void mg_set_non_blocking_mode(SOCKET fd) {
@@ -3434,7 +3436,7 @@ static char *mg_ssi(const char *path, const char *root, int depth) {
           snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path, arg);
           if (depth < MG_MAX_SSI_DEPTH &&
               (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
-            mg_iobuf_append(&b, data, strlen(data), align);
+            mg_iobuf_add(&b, b.len, data, strlen(data), align);
             free(data);
           } else {
             LOG(LL_ERROR, ("%s: file=%s error or too deep", path, arg));
@@ -3444,7 +3446,7 @@ static char *mg_ssi(const char *path, const char *root, int depth) {
           snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
           if (depth < MG_MAX_SSI_DEPTH &&
               (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
-            mg_iobuf_append(&b, data, strlen(data), align);
+            mg_iobuf_add(&b, b.len, data, strlen(data), align);
             free(data);
           } else {
             LOG(LL_ERROR, ("%s: virtual=%s error or too deep", path, arg));
@@ -3452,13 +3454,13 @@ static char *mg_ssi(const char *path, const char *root, int depth) {
         } else {
           // Unknown SSI tag
           LOG(LL_INFO, ("Unknown SSI tag: %.*s", (int) len, buf));
-          mg_iobuf_append(&b, buf, len, align);
+          mg_iobuf_add(&b, b.len, buf, len, align);
         }
         intag = 0;
         len = 0;
       } else if (ch == '<') {
         intag = 1;
-        if (len > 0) mg_iobuf_append(&b, buf, len, align);
+        if (len > 0) mg_iobuf_add(&b, b.len, buf, len, align);
         len = 0;
         buf[len++] = (char) (ch & 0xff);
       } else if (intag) {
@@ -3472,13 +3474,13 @@ static char *mg_ssi(const char *path, const char *root, int depth) {
       } else {
         buf[len++] = (char) (ch & 0xff);
         if (len >= sizeof(buf)) {
-          mg_iobuf_append(&b, buf, len, align);
+          mg_iobuf_add(&b, b.len, buf, len, align);
           len = 0;
         }
       }
     }
-    if (len > 0) mg_iobuf_append(&b, buf, len, align);
-    if (b.len > 0) mg_iobuf_append(&b, "", 1, align);  // nul-terminate
+    if (len > 0) mg_iobuf_add(&b, b.len, buf, len, align);
+    if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1, align);  // nul-terminate
     fclose(fp);
   }
   (void) depth;
@@ -4749,11 +4751,11 @@ size_t mg_ws_wrap(struct mg_connection *c, size_t len, int op) {
   size_t header_len = mkhdr(len, op, c->is_client, header);
 
   // NOTE: order of operations is important!
-  mg_iobuf_append(&c->send, NULL, header_len, MG_IO_SIZE);  // Add header space
-  p = &c->send.buf[c->send.len - len];                      // p points to data
-  memmove(p, p - header_len, len);                          // Shift data
-  memcpy(p - header_len, header, header_len);               // Prepend header
-  mg_ws_mask(c, len);                                       // Mask data
+  mg_iobuf_add(&c->send, c->send.len, NULL, header_len, MG_IO_SIZE);
+  p = &c->send.buf[c->send.len - len];         // p points to data
+  memmove(p, p - header_len, len);             // Shift data
+  memcpy(p - header_len, header, header_len);  // Prepend header
+  mg_ws_mask(c, len);                          // Mask data
 
   return c->send.len;
 }
diff --git a/mongoose.h b/mongoose.h
index 16efc5d4497fdc138862bc1fdc710c23c1bf0005..111993373ad2ec4eea22f90da634ab75b49ee889 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -628,14 +628,15 @@ const char *mg_url_uri(const char *url);
 #include <stddef.h>
 
 struct mg_iobuf {
-  unsigned char *buf;
-  size_t size, len;
+  unsigned char *buf;  // Data
+  size_t size;         // Total size available
+  size_t len;          // Current number of bytes
 };
 
 int mg_iobuf_init(struct mg_iobuf *, size_t);
 int mg_iobuf_resize(struct mg_iobuf *, size_t);
 void mg_iobuf_free(struct mg_iobuf *);
-size_t mg_iobuf_append(struct mg_iobuf *, const void *, size_t, size_t);
+size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *, size_t, size_t);
 size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len);
 
 int mg_base64_update(unsigned char p, char *to, int len);
diff --git a/src/iobuf.c b/src/iobuf.c
index c1550676b9aecca9b0332c6c7ddddf0e8a872b39..4856e2bb00a62d42414621b5fa441c35c2dcdd5c 100644
--- a/src/iobuf.c
+++ b/src/iobuf.c
@@ -42,8 +42,8 @@ int mg_iobuf_init(struct mg_iobuf *io, size_t size) {
   return mg_iobuf_resize(io, size);
 }
 
-size_t mg_iobuf_append(struct mg_iobuf *io, const void *buf, size_t len,
-                       size_t chunk_size) {
+size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf,
+                    size_t len, size_t chunk_size) {
   size_t new_size = io->len + len;
   if (new_size > io->size) {
     new_size += chunk_size;             // Make sure that io->size
@@ -51,7 +51,9 @@ size_t mg_iobuf_append(struct mg_iobuf *io, const void *buf, size_t len,
     mg_iobuf_resize(io, new_size);      // Attempt to realloc
     if (new_size != io->size) len = 0;  // Realloc failure, append nothing
   }
-  if (buf != NULL) memmove(io->buf + io->len, buf, len);
+  if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs);
+  if (buf != NULL) memmove(io->buf + ofs, buf, len);
+  if (ofs > io->len) io->len += ofs - io->len;
   io->len += len;
   return len;
 }
diff --git a/src/iobuf.h b/src/iobuf.h
index 19b76517897d19f15fad32d2cea9d77b3c5e724d..c456e3beefa52c77e154bf18d1d4bf655129ee41 100644
--- a/src/iobuf.h
+++ b/src/iobuf.h
@@ -3,12 +3,13 @@
 #include <stddef.h>
 
 struct mg_iobuf {
-  unsigned char *buf;
-  size_t size, len;
+  unsigned char *buf;  // Data
+  size_t size;         // Total size available
+  size_t len;          // Current number of bytes
 };
 
 int mg_iobuf_init(struct mg_iobuf *, size_t);
 int mg_iobuf_resize(struct mg_iobuf *, size_t);
 void mg_iobuf_free(struct mg_iobuf *);
-size_t mg_iobuf_append(struct mg_iobuf *, const void *, size_t, size_t);
+size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *, size_t, size_t);
 size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len);
diff --git a/src/sock.c b/src/sock.c
index eca289375111c346daf1f5d88111fc5d2365acf6..f72bd5b67771223e170040ba182bb57ab8e399ae 100644
--- a/src/sock.c
+++ b/src/sock.c
@@ -107,7 +107,7 @@ static long mg_sock_send(struct mg_connection *c, const void *buf, size_t len) {
 
 bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
   return c->is_udp ? mg_sock_send(c, buf, len) > 0
-                   : mg_iobuf_append(&c->send, buf, len, MG_IO_SIZE);
+                   : mg_iobuf_add(&c->send, c->send.len, buf, len, MG_IO_SIZE);
 }
 
 static void mg_set_non_blocking_mode(SOCKET fd) {
diff --git a/src/ssi.c b/src/ssi.c
index 931828296faa1e73439c46177ac3d9ec49d23b1b..5bf16f22c67cbe18fbebb22994721bff2c5212fe 100644
--- a/src/ssi.c
+++ b/src/ssi.c
@@ -24,7 +24,7 @@ static char *mg_ssi(const char *path, const char *root, int depth) {
           snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path, arg);
           if (depth < MG_MAX_SSI_DEPTH &&
               (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
-            mg_iobuf_append(&b, data, strlen(data), align);
+            mg_iobuf_add(&b, b.len, data, strlen(data), align);
             free(data);
           } else {
             LOG(LL_ERROR, ("%s: file=%s error or too deep", path, arg));
@@ -34,7 +34,7 @@ static char *mg_ssi(const char *path, const char *root, int depth) {
           snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
           if (depth < MG_MAX_SSI_DEPTH &&
               (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
-            mg_iobuf_append(&b, data, strlen(data), align);
+            mg_iobuf_add(&b, b.len, data, strlen(data), align);
             free(data);
           } else {
             LOG(LL_ERROR, ("%s: virtual=%s error or too deep", path, arg));
@@ -42,13 +42,13 @@ static char *mg_ssi(const char *path, const char *root, int depth) {
         } else {
           // Unknown SSI tag
           LOG(LL_INFO, ("Unknown SSI tag: %.*s", (int) len, buf));
-          mg_iobuf_append(&b, buf, len, align);
+          mg_iobuf_add(&b, b.len, buf, len, align);
         }
         intag = 0;
         len = 0;
       } else if (ch == '<') {
         intag = 1;
-        if (len > 0) mg_iobuf_append(&b, buf, len, align);
+        if (len > 0) mg_iobuf_add(&b, b.len, buf, len, align);
         len = 0;
         buf[len++] = (char) (ch & 0xff);
       } else if (intag) {
@@ -62,13 +62,13 @@ static char *mg_ssi(const char *path, const char *root, int depth) {
       } else {
         buf[len++] = (char) (ch & 0xff);
         if (len >= sizeof(buf)) {
-          mg_iobuf_append(&b, buf, len, align);
+          mg_iobuf_add(&b, b.len, buf, len, align);
           len = 0;
         }
       }
     }
-    if (len > 0) mg_iobuf_append(&b, buf, len, align);
-    if (b.len > 0) mg_iobuf_append(&b, "", 1, align);  // nul-terminate
+    if (len > 0) mg_iobuf_add(&b, b.len, buf, len, align);
+    if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1, align);  // nul-terminate
     fclose(fp);
   }
   (void) depth;
diff --git a/src/ws.c b/src/ws.c
index 54f71f7de7aec438817a595a973942630aac2d03..e6c5db14a285d782a17a3dedc41c081998cc0ee2 100644
--- a/src/ws.c
+++ b/src/ws.c
@@ -269,11 +269,11 @@ size_t mg_ws_wrap(struct mg_connection *c, size_t len, int op) {
   size_t header_len = mkhdr(len, op, c->is_client, header);
 
   // NOTE: order of operations is important!
-  mg_iobuf_append(&c->send, NULL, header_len, MG_IO_SIZE);  // Add header space
-  p = &c->send.buf[c->send.len - len];                      // p points to data
-  memmove(p, p - header_len, len);                          // Shift data
-  memcpy(p - header_len, header, header_len);               // Prepend header
-  mg_ws_mask(c, len);                                       // Mask data
+  mg_iobuf_add(&c->send, c->send.len, NULL, header_len, MG_IO_SIZE);
+  p = &c->send.buf[c->send.len - len];         // p points to data
+  memmove(p, p - header_len, len);             // Shift data
+  memcpy(p - header_len, header, header_len);  // Prepend header
+  mg_ws_mask(c, len);                          // Mask data
 
   return c->send.len;
 }
diff --git a/test/unit_test.c b/test/unit_test.c
index 003101e14b6efaaec6f5530260183a3dc7a353b2..f3f8c2b9c06a2506f741c4052ae12eeb29dae128 100644
--- a/test/unit_test.c
+++ b/test/unit_test.c
@@ -208,12 +208,22 @@ static void test_iobuf(void) {
   ASSERT(io.buf == NULL && io.size == 0 && io.len == 0);
   mg_iobuf_resize(&io, 1);
   ASSERT(io.buf != NULL && io.size == 1 && io.len == 0);
-  mg_iobuf_append(&io, "hi", 2, 10);
-  ASSERT(io.buf != NULL && io.size == 10 && io.len == 2);
-  ASSERT(memcmp(io.buf, "hi", 2) == 0);
-  mg_iobuf_append(&io, "!", 1, 10);
-  ASSERT(io.buf != NULL && io.size == 10 && io.len == 3);
-  ASSERT(memcmp(io.buf, "hi!", 3) == 0);
+  ASSERT(memcmp(io.buf, "\x00", 1) == 0);
+  mg_iobuf_add(&io, 3, "hi", 2, 10);
+  ASSERT(io.buf != NULL && io.size == 10 && io.len == 5);
+  ASSERT(memcmp(io.buf, "\x00\x00\x00hi", 5) == 0);
+  mg_iobuf_add(&io, io.len, "!", 1, 10);
+  ASSERT(io.buf != NULL && io.size == 10 && io.len == 6);
+  ASSERT(memcmp(io.buf, "\x00\x00\x00hi!", 6) == 0);
+  mg_iobuf_add(&io, 0, "x", 1, 10);
+  ASSERT(memcmp(io.buf, "x\x00\x00\x00hi!", 7) == 0);
+  ASSERT(io.buf != NULL && io.size == 10 && io.len == 7);
+  mg_iobuf_del(&io, 1, 3);
+  ASSERT(io.buf != NULL && io.size == 10 && io.len == 4);
+  ASSERT(memcmp(io.buf, "xhi!", 3) == 0);
+  mg_iobuf_del(&io, 10, 100);
+  ASSERT(io.buf != NULL && io.size == 10 && io.len == 4);
+  ASSERT(memcmp(io.buf, "xhi!", 3) == 0);
   free(io.buf);
 }