diff --git a/docs/c-api/http_server.h/intro.md b/docs/c-api/http_server.h/intro.md
index 0879399c74a79dda6f532b0ebc7da84454e529c2..bfa6d7a3e69768947182302d7e1802ff8174c7a3 100644
--- a/docs/c-api/http_server.h/intro.md
+++ b/docs/c-api/http_server.h/intro.md
@@ -16,6 +16,7 @@ items:
   - { name: mg_send_http_chunk.md }
   - { name: mg_printf_http_chunk.md }
   - { name: mg_send_response_line.md }
+  - { name: mg_http_send_redirect.md }
   - { name: mg_send_head.md }
   - { name: mg_printf_html_escape.md }
   - { name: mg_fu_fname_fn.md }
diff --git a/docs/c-api/http_server.h/mg_http_send_redirect.md b/docs/c-api/http_server.h/mg_http_send_redirect.md
new file mode 100644
index 0000000000000000000000000000000000000000..206f3e9de7e0f905bc2bbdb5b4441dfd39693dd5
--- /dev/null
+++ b/docs/c-api/http_server.h/mg_http_send_redirect.md
@@ -0,0 +1,24 @@
+---
+title: "mg_http_send_redirect()"
+decl_name: "mg_http_send_redirect"
+symbol_kind: "func"
+signature: |
+  void mg_http_send_redirect(struct mg_connection *nc, int status_code,
+                             const struct mg_str location,
+                             const struct mg_str extra_headers);
+---
+
+Sends a redirect response.
+`status_code` should be either 301 or 302 and `location` point to the
+new location.
+If `extra_headers` is not empty, then `extra_headers` are also sent
+after the reponse line. `extra_headers` must NOT end end with new line.
+Example:
+
+     mg_send_response_line(nc, 200, "Access-Control-Allow-Origin: *");
+
+Will result in:
+
+     HTTP/1.1 200 OK\r\n
+     Access-Control-Allow-Origin: *\r\n 
+
diff --git a/docs/c-api/http_server.h/mg_send_response_line.md b/docs/c-api/http_server.h/mg_send_response_line.md
index 213927dd53a802cc82f9ad61962b04bc082b254e..4bd22639a2649e2310a2fd387d3b272c668c07df 100644
--- a/docs/c-api/http_server.h/mg_send_response_line.md
+++ b/docs/c-api/http_server.h/mg_send_response_line.md
@@ -3,7 +3,7 @@ title: "mg_send_response_line()"
 decl_name: "mg_send_response_line"
 symbol_kind: "func"
 signature: |
-  void mg_send_response_line(struct mg_connection *c, int status_code,
+  void mg_send_response_line(struct mg_connection *nc, int status_code,
                              const char *extra_headers);
 ---
 
diff --git a/docs/c-api/util.h/intro.md b/docs/c-api/util.h/intro.md
index 2d8024a0536755fe2f1bda0c901c09fe32fa9708..88b8afbc86c3b8763fff0a7b5c3d895c6621cdfe 100644
--- a/docs/c-api/util.h/intro.md
+++ b/docs/c-api/util.h/intro.md
@@ -17,6 +17,7 @@ items:
   - { name: mg_sock_addr_to_str.md }
   - { name: mg_hexdump.md }
   - { name: mg_hexdump_connection.md }
+  - { name: mg_asprintf.md }
   - { name: mg_avprintf.md }
   - { name: mg_is_big_endian.md }
   - { name: mg_next_comma_list_entry.md }
diff --git a/docs/c-api/util.h/mg_asprintf.md b/docs/c-api/util.h/mg_asprintf.md
new file mode 100644
index 0000000000000000000000000000000000000000..10dd17460336384aa6903ca58883585484d45e10
--- /dev/null
+++ b/docs/c-api/util.h/mg_asprintf.md
@@ -0,0 +1,22 @@
+---
+title: "mg_asprintf()"
+decl_name: "mg_asprintf"
+symbol_kind: "func"
+signature: |
+  int mg_asprintf(char **buf, size_t size, const char *fmt, ...);
+---
+
+Prints message to the buffer. If the buffer is large enough to hold the
+message, it returns buffer. If buffer is to small, it allocates a large
+enough buffer on heap and returns allocated buffer.
+This is a supposed use case:
+
+   char buf[5], *p = buf;
+   mg_avprintf(&p, sizeof(buf), "%s", "hi there");
+   use_p_somehow(p);
+   if (p != buf) {
+     free(p);
+   }
+
+The purpose of this is to avoid malloc-ing if generated strings are small. 
+
diff --git a/docs/c-api/util.h/mg_avprintf.md b/docs/c-api/util.h/mg_avprintf.md
index 9a70d8400685982c3b7dd3bdcf99391d4afd43ea..aff55db0126f1d5b6b49ab302616f64ae64e1144 100644
--- a/docs/c-api/util.h/mg_avprintf.md
+++ b/docs/c-api/util.h/mg_avprintf.md
@@ -6,17 +6,5 @@ signature: |
   int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap);
 ---
 
-Prints message to the buffer. If the buffer is large enough to hold the
-message, it returns buffer. If buffer is to small, it allocates a large
-enough buffer on heap and returns allocated buffer.
-This is a supposed use case:
-
-   char buf[5], *p = buf;
-   p = mg_avprintf(&p, sizeof(buf), "%s", "hi there");
-   use_p_somehow(p);
-   if (p != buf) {
-     free(p);
-   }
-
-The purpose of this is to avoid malloc-ing if generated strings are small. 
+Same as mg_asprintf, but takes varargs list. 
 
diff --git a/examples/connected_device_2/server.c b/examples/connected_device_2/server.c
index 37281c5bf8f8f81c514baaf8d3ec8b8c0ac2d15f..26bf30924394ae5c11cf97e9bc186d17bd826983 100644
--- a/examples/connected_device_2/server.c
+++ b/examples/connected_device_2/server.c
@@ -20,7 +20,7 @@ static void handle_save(struct mg_connection *nc, struct http_message *hm) {
                   sizeof(s_settings.setting2));
 
   // Send response
-  mg_printf(nc, "%s", "HTTP/1.1 302 OK\r\nLocation: /\r\n\r\n");
+  mg_http_send_redirect(nc, 302, mg_mk_str("/"), mg_mk_str(NULL));
 }
 
 static void handle_ssi_call(struct mg_connection *nc, const char *param) {
diff --git a/examples/connected_device_3/server.c b/examples/connected_device_3/server.c
index fc9f907db7cd1ceeb34c481d00332aa54814770a..d939716acc2211f7303c31fe2f2805ef66c9d40b 100644
--- a/examples/connected_device_3/server.c
+++ b/examples/connected_device_3/server.c
@@ -20,7 +20,7 @@ static void handle_save(struct mg_connection *nc, struct http_message *hm) {
                   sizeof(s_settings.setting2));
 
   // Send response
-  mg_printf(nc, "%s", "HTTP/1.1 302 OK\r\nLocation: /\r\n\r\n");
+  mg_http_send_redirect(nc, 302, mg_mk_str("/"), mg_mk_str(NULL));
 }
 
 static void handle_get_cpu_usage(struct mg_connection *nc) {
diff --git a/examples/connected_device_4/server.c b/examples/connected_device_4/server.c
index f836203d4316e0298c656c7abe5f28966a115a24..854aa13be2e16efb35eb1421eff691df0aa03e44 100644
--- a/examples/connected_device_4/server.c
+++ b/examples/connected_device_4/server.c
@@ -20,7 +20,7 @@ static void handle_save(struct mg_connection *nc, struct http_message *hm) {
                   sizeof(s_settings.setting2));
 
   // Send response
-  mg_printf(nc, "%s", "HTTP/1.1 302 OK\r\nLocation: /\r\n\r\n");
+  mg_http_send_redirect(nc, 302, mg_mk_str("/"), mg_mk_str(NULL));
 }
 
 static void handle_get_cpu_usage(struct mg_connection *nc) {
diff --git a/examples/cookie_auth/cookie_auth.c b/examples/cookie_auth/cookie_auth.c
index 2c0811237a56fc78e95237942040801246f12d99..d0bc672ce8198e191ea5d168c696c6d48cdda354 100644
--- a/examples/cookie_auth/cookie_auth.c
+++ b/examples/cookie_auth/cookie_auth.c
@@ -120,12 +120,6 @@ static struct session *create_session(const char *user,
   return s;
 }
 
-static void set_session_cookie(struct mg_connection *nc,
-                               const struct session *s) {
-  mg_printf(nc, "Set-Cookie: %s=%" INT64_X_FMT "; path=/\r\n",
-            SESSION_COOKIE_NAME, s->id);
-}
-
 /*
  * If requested via GET, serves the login page.
  * If requested via POST (form submission), checks password and logs user in.
@@ -143,10 +137,11 @@ static void login_handler(struct mg_connection *nc, int ev, void *p) {
     if (ul > 0 && pl > 0) {
       if (check_pass(user, pass)) {
         struct session *s = create_session(user, hm);
-        mg_printf(nc, "HTTP/1.0 302 Found\r\n");
-        set_session_cookie(nc, s);
-        mg_printf(nc, "Location: /\r\n");
-        mg_printf(nc, "\r\nHello, %s!\r\n", s->user);
+        char shead[100];
+        snprintf(shead, sizeof(shead),
+                 "Set-Cookie: %s=%" INT64_X_FMT "; path=/", SESSION_COOKIE_NAME,
+                 s->id);
+        mg_http_send_redirect(nc, 302, mg_mk_str("/"), mg_mk_str(shead));
         fprintf(stderr, "%s logged in, sid %" INT64_X_FMT "\n", s->user, s->id);
       } else {
         mg_printf(nc, "HTTP/1.0 403 Unauthorized\r\n\r\nWrong password.\r\n");
@@ -165,13 +160,9 @@ static void login_handler(struct mg_connection *nc, int ev, void *p) {
  */
 static void logout_handler(struct mg_connection *nc, int ev, void *p) {
   struct http_message *hm = (struct http_message *) p;
-  mg_printf(nc,
-            "HTTP/1.0 302 Found\r\n"
-            "Set-Cookie: %s=\r\n"
-            "Location: /\r\n"
-            "\r\n"
-            "Logged out",
-            SESSION_COOKIE_NAME);
+  char shead[100];
+  snprintf(shead, sizeof(shead), "Set-Cookie: %s=", SESSION_COOKIE_NAME);
+  mg_http_send_redirect(nc, 302, mg_mk_str("/"), mg_mk_str(shead));
   struct session *s = get_session(hm);
   if (s != NULL) {
     fprintf(stderr, "%s logged out, session %" INT64_X_FMT " destroyed\n",
@@ -203,11 +194,8 @@ static void ev_handler(struct mg_connection *nc, int ev, void *p) {
       struct session *s = get_session(hm);
       /* Ask the user to log in if they did not present a valid cookie. */
       if (s == NULL) {
-        mg_printf(nc,
-                  "HTTP/1.0 302 Found\r\n"
-                  "Location: /login.html\r\n"
-                  "\r\n"
-                  "Please log in");
+        mg_http_send_redirect(nc, 302, mg_mk_str("/login.html"),
+                              mg_mk_str(NULL));
         nc->flags |= MG_F_SEND_AND_CLOSE;
         break;
       }
diff --git a/mongoose.c b/mongoose.c
index 94b7227962699f05e60400bd9ce8003a606aa45b..111930bfd2101d4828c90dd28865ae46f55c10d9 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -5336,6 +5336,28 @@ void mg_send_response_line(struct mg_connection *nc, int status_code,
   mg_send_response_line_s(nc, status_code, mg_mk_str(extra_headers));
 }
 
+void mg_http_send_redirect(struct mg_connection *nc, int status_code,
+                           const struct mg_str location,
+                           const struct mg_str extra_headers) {
+  char bbody[100], *pbody = bbody;
+  int bl = mg_asprintf(&pbody, sizeof(bbody),
+                       "<p>Moved <a href='%.*s'>here</a>.\r\n",
+                       (int) location.len, location.p);
+  char bhead[150], *phead = bhead;
+  mg_asprintf(&phead, sizeof(bhead),
+              "Location: %.*s\r\n"
+              "Content-Type: text/html\r\n"
+              "Content-Length: %d\r\n"
+              "Cache-Control: no-cache\r\n"
+              "%.*s%s",
+              (int) location.len, location.p, bl, (int) extra_headers.len,
+              extra_headers.p, (extra_headers.len > 0 ? "\r\n" : ""));
+  mg_send_response_line(nc, status_code, phead);
+  if (phead != bhead) MG_FREE(phead);
+  mg_send(nc, pbody, bl);
+  if (pbody != bbody) MG_FREE(pbody);
+}
+
 void mg_send_head(struct mg_connection *c, int status_code,
                   int64_t content_length, const char *extra_headers) {
   mg_send_response_line(c, status_code, extra_headers);
@@ -7754,6 +7776,15 @@ int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap) {
   return len;
 }
 
+int mg_asprintf(char **buf, size_t size, const char *fmt, ...) {
+  int ret;
+  va_list ap;
+  va_start(ap, fmt);
+  ret = mg_avprintf(buf, size, fmt, ap);
+  va_end(ap);
+  return ret;
+}
+
 #if !defined(MG_DISABLE_HEXDUMP)
 void mg_hexdump_connection(struct mg_connection *nc, const char *path,
                            const void *buf, int num_bytes, int ev) {
diff --git a/mongoose.h b/mongoose.h
index 37dd0572480ec8d8e83a59fff090b466da10cf12..825a7cf0ca5d350d5fb9fed3026fe2a4c51935cc 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -2009,6 +2009,7 @@ int mg_hexdump(const void *buf, int len, char *dst, int dst_len);
  */
 void mg_hexdump_connection(struct mg_connection *nc, const char *path,
                            const void *buf, int num_bytes, int ev);
+
 /*
  * Prints message to the buffer. If the buffer is large enough to hold the
  * message, it returns buffer. If buffer is to small, it allocates a large
@@ -2016,7 +2017,7 @@ void mg_hexdump_connection(struct mg_connection *nc, const char *path,
  * This is a supposed use case:
  *
  *    char buf[5], *p = buf;
- *    p = mg_avprintf(&p, sizeof(buf), "%s", "hi there");
+ *    mg_avprintf(&p, sizeof(buf), "%s", "hi there");
  *    use_p_somehow(p);
  *    if (p != buf) {
  *      free(p);
@@ -2024,6 +2025,9 @@ void mg_hexdump_connection(struct mg_connection *nc, const char *path,
  *
  * The purpose of this is to avoid malloc-ing if generated strings are small.
  */
+int mg_asprintf(char **buf, size_t size, const char *fmt, ...);
+
+/* Same as mg_asprintf, but takes varargs list. */
 int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap);
 
 /*
@@ -2800,9 +2804,28 @@ void mg_printf_http_chunk(struct mg_connection *nc, const char *fmt, ...);
  *      HTTP/1.1 200 OK\r\n
  *      Access-Control-Allow-Origin: *\r\n
  */
-void mg_send_response_line(struct mg_connection *c, int status_code,
+void mg_send_response_line(struct mg_connection *nc, int status_code,
                            const char *extra_headers);
 
+/*
+ * Sends a redirect response.
+ * `status_code` should be either 301 or 302 and `location` point to the
+ * new location.
+ * If `extra_headers` is not empty, then `extra_headers` are also sent
+ * after the reponse line. `extra_headers` must NOT end end with new line.
+ * Example:
+ *
+ *      mg_send_response_line(nc, 200, "Access-Control-Allow-Origin: *");
+ *
+ * Will result in:
+ *
+ *      HTTP/1.1 200 OK\r\n
+ *      Access-Control-Allow-Origin: *\r\n
+ */
+void mg_http_send_redirect(struct mg_connection *nc, int status_code,
+                           const struct mg_str location,
+                           const struct mg_str extra_headers);
+
 /*
  * Sends the response line and headers.
  * This function sends the response line with the `status_code`, and