diff --git a/examples/restful_server/restful_server.c b/examples/restful_server/restful_server.c
index 85e52e933fc2c067cef7238f6202b6c745e47678..27ada98bb3a1401897c03c3b4f886150744bae94 100644
--- a/examples/restful_server/restful_server.c
+++ b/examples/restful_server/restful_server.c
@@ -74,7 +74,7 @@ int main(int argc, char *argv[]) {
 #endif
     } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
       s_http_server_opts.global_auth_file = argv[++i];
-    } else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
+    } else if (strcmp(argv[i], "-A") == 0 && i + 1 < argc) {
       s_http_server_opts.per_directory_auth_file = argv[++i];
     } else if (strcmp(argv[i], "-r") == 0 && i + 1 < argc) {
       s_http_server_opts.url_rewrites = argv[++i];
diff --git a/mongoose.c b/mongoose.c
index 73a1f1791eb5191bfc16f6f420dc0fa6d6a12ccc..c24e740ec59337357333799f4a25785e19b1b2f7 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -4488,6 +4488,12 @@ void mg_send_response_line(struct mg_connection *nc, int status_code,
     case 206:
       status_message = "Partial Content";
       break;
+    case 301:
+      status_message = "Moved";
+      break;
+    case 302:
+      status_message = "Found";
+      break;
     case 416:
       status_message = "Requested range not satisfiable";
       break;
@@ -5502,6 +5508,34 @@ MG_INTERNAL int find_index_file(char *path, size_t path_len, const char *list,
   return found;
 }
 
+static int send_port_based_redirect(struct mg_connection *c,
+                                    struct http_message *hm,
+                                    const struct mg_serve_http_opts *opts) {
+  const char *rewrites = opts->url_rewrites;
+  struct mg_str a, b;
+  char local_port[20] = {'%'};
+
+#ifndef MG_ESP8266
+  mg_sock_to_str(c->sock, local_port + 1, sizeof(local_port) - 1,
+                 MG_SOCK_STRINGIFY_PORT);
+#else
+  /* TODO(lsm): remove when mg_sock_to_str() is implemented in LWIP codepath */
+  snprintf(local_port, sizeof(local_port), "%s", "%0");
+#endif
+
+  while ((rewrites = mg_next_comma_list_entry(rewrites, &a, &b)) != NULL) {
+    if (mg_vcmp(&a, local_port) == 0) {
+      mg_send_response_line(c, 301, NULL);
+      mg_printf(c, "Content-Length: 0\r\nLocation: %.*s%.*s\r\n\r\n",
+                (int) b.len, b.p, (int) (hm->proto.p - hm->uri.p - 1),
+                hm->uri.p);
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
 static void uri_to_path(struct http_message *hm, char *buf, size_t buf_len,
                         const struct mg_serve_http_opts *opts) {
   char uri[MG_MAX_PATH];
@@ -6132,6 +6166,10 @@ void mg_serve_http(struct mg_connection *nc, struct http_message *hm,
   char path[MG_MAX_PATH];
   struct mg_str *hdr;
 
+  if (send_port_based_redirect(nc, hm, &opts)) {
+    return;
+  }
+
   if (opts.document_root == NULL) {
     opts.document_root = ".";
   }
diff --git a/mongoose.h b/mongoose.h
index f8dd00fc9607a55c8c3d6f1433079e6f6c3d02a2..a1674203d1ae83cff3f9b67163707a9b8f0a3379 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -1772,6 +1772,13 @@ struct mg_serve_http_opts {
    * If uri_pattern starts with `@` symbol, then Mongoose compares it with the
    * HOST header of the request. If they are equal, Mongoose sets document root
    * to `file_or_directory_path`, implementing virtual hosts support.
+   * Example: `@foo.com=/document/root/for/foo.com`
+   *
+   * If `uri_pattern` starts with `%` symbol, then Mongoose compares it with
+   * the listening port. If they match, then Mongoose issues a 301 redirect.
+   * For example, to redirect all HTTP requests to the
+   * HTTPS port, do `%80=https://my.site.com`. Note that the request URI is
+   * automatically appended to the redirect location.
    */
   const char *url_rewrites;