diff --git a/mongoose.c b/mongoose.c
index 8b948f92889fd1cc3c22598f4e45b47fa9619433..d58085369a52a1deb3fdea69318da974d2d2d52d 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -2488,11 +2488,13 @@ void mg_if_connect_cb(struct mg_connection *nc, int err) {
  *    either failure (and dealloc the connection)
  *    or success (and proceed with connect()
  */
-static void resolve_cb(struct mg_dns_message *msg, void *data) {
+static void resolve_cb(struct mg_dns_message *msg, void *data,
+                       enum mg_resolve_err e) {
   struct mg_connection *nc = (struct mg_connection *) data;
   int i;
   int failure = -1;
 
+  nc->flags &= ~MG_F_RESOLVING;
   if (msg != NULL) {
     /*
      * Take the first DNS A answer and run...
@@ -2512,6 +2514,11 @@ static void resolve_cb(struct mg_dns_message *msg, void *data) {
     }
   }
 
+  if (e == MG_RESOLVE_TIMEOUT) {
+    double now = time(NULL);
+    mg_call(nc, NULL, MG_EV_TIMER, &now);
+  }
+
   /*
    * If we get there was no MG_DNS_A_RECORD in the answer
    */
@@ -2557,12 +2564,18 @@ struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
      * DNS resolution is required for host.
      * mg_parse_address() fills port in nc->sa, which we pass to resolve_cb()
      */
-    if (mg_resolve_async(nc->mgr, host, MG_DNS_A_RECORD, resolve_cb, nc) != 0) {
+    struct mg_connection *dns_conn = NULL;
+    struct mg_resolve_async_opts o;
+    memset(&o, 0, sizeof(o));
+    o.dns_conn = &dns_conn;
+    if (mg_resolve_async_opt(nc->mgr, host, MG_DNS_A_RECORD, resolve_cb, nc,
+                             o) != 0) {
       MG_SET_PTRPTR(opts.error_string, "cannot schedule DNS lookup");
       mg_destroy_conn(nc);
       return NULL;
     }
-
+    nc->priv_2 = dns_conn;
+    nc->flags |= MG_F_RESOLVING;
     return nc;
 #else
     MG_SET_PTRPTR(opts.error_string, "Resolver is disabled");
@@ -2700,6 +2713,21 @@ void mg_forward(struct mg_connection *from, struct mg_connection *to) {
   mg_send(to, from->recv_mbuf.buf, from->recv_mbuf.len);
   mbuf_remove(&from->recv_mbuf, from->recv_mbuf.len);
 }
+
+double mg_set_timer(struct mg_connection *c, double timestamp) {
+  double result = c->ev_timer_time;
+  c->ev_timer_time = timestamp;
+  /*
+   * If this connection is resolving, it's not in the list of active
+   * connections, so not processed yet. It has a DNS resolver connection
+   * linked to it. Set up a timer for the DNS connection.
+   */
+  DBG(("%p %p %d", c, c->priv_2, c->flags & MG_F_RESOLVING));
+  if ((c->flags & MG_F_RESOLVING) && c->priv_2 != NULL) {
+    ((struct mg_connection *) c->priv_2)->ev_timer_time = timestamp;
+  }
+  return result;
+}
 #ifdef NS_MODULE_LINES
 #line 1 "src/net_if_socket.c"
 /**/
@@ -7835,6 +7863,7 @@ struct mg_resolve_async_request {
   void *data;
   time_t timeout;
   int max_retries;
+  enum mg_resolve_err err;
 
   /* state */
   time_t last_time;
@@ -7964,6 +7993,7 @@ static void mg_resolve_async_eh(struct mg_connection *nc, int ev, void *data) {
     case MG_EV_CONNECT:
     case MG_EV_POLL:
       if (req->retries > req->max_retries) {
+        req->err = MG_RESOLVE_EXCEEDED_RETRY_COUNT;
         nc->flags |= MG_F_CLOSE_IMMEDIATELY;
         break;
       }
@@ -7977,9 +8007,11 @@ static void mg_resolve_async_eh(struct mg_connection *nc, int ev, void *data) {
       msg = (struct mg_dns_message *) MG_MALLOC(sizeof(*msg));
       if (mg_parse_dns(nc->recv_mbuf.buf, *(int *) data, msg) == 0 &&
           msg->num_answers > 0) {
-        req->callback(msg, req->data);
+        req->callback(msg, req->data, MG_RESOLVE_OK);
         nc->user_data = NULL;
         MG_FREE(req);
+      } else {
+        req->err = MG_RESOLVE_NO_ANSWERS;
       }
       MG_FREE(msg);
       nc->flags |= MG_F_CLOSE_IMMEDIATELY;
@@ -7992,10 +8024,14 @@ static void mg_resolve_async_eh(struct mg_connection *nc, int ev, void *data) {
       nc->flags &= ~MG_F_CLOSE_IMMEDIATELY;
       mbuf_remove(&nc->send_mbuf, nc->send_mbuf.len);
       break;
+    case MG_EV_TIMER:
+      req->err = MG_RESOLVE_TIMEOUT;
+      nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+      break;
     case MG_EV_CLOSE:
       /* If we got here with request still not done, fire an error callback. */
       if (req != NULL) {
-        req->callback(NULL, req->data);
+        req->callback(NULL, req->data, req->err);
         nc->user_data = NULL;
         MG_FREE(req);
       }
@@ -8017,7 +8053,7 @@ int mg_resolve_async_opt(struct mg_mgr *mgr, const char *name, int query,
   struct mg_connection *dns_nc;
   const char *nameserver = opts.nameserver_url;
 
-  DBG(("%s %d", name, query));
+  DBG(("%s %d %p", name, query, opts.dns_conn));
 
   /* resolve with DNS */
   req = (struct mg_resolve_async_request *) MG_CALLOC(1, sizeof(*req));
@@ -8050,6 +8086,9 @@ int mg_resolve_async_opt(struct mg_mgr *mgr, const char *name, int query,
     return -1;
   }
   dns_nc->user_data = req;
+  if (opts.dns_conn != NULL) {
+    *opts.dns_conn = dns_nc;
+  }
 
   return 0;
 }
diff --git a/mongoose.h b/mongoose.h
index 14fae65e52968496243c0d81b28543483fd692a8..45809380cac73095581ff3cb0f82a296c325fa9b 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -1063,6 +1063,35 @@ enum v7_err mg_enable_javascript(struct mg_mgr *m, struct v7 *v7,
                                  const char *init_js_file_name);
 #endif
 
+/*
+ * Schedule MG_EV_TIMER event to be delivered at `timestamp` time.
+ * `timestamp` is a UNIX time (a number of seconds since Epoch). It is
+ * `double` instead of `time_t` to allow for sub-second precision.
+ * Return the old timer value.
+ *
+ * Example: set connect timeout to 1.5 seconds:
+ *
+ * ```
+ *  c = mg_connect(&mgr, "cesanta.com", ev_handler);
+ *  mg_set_timer(c, time(NULL) + 1.5);
+ *  ...
+ *
+ *  void ev_handler(struct mg_connection *c, int ev, void *ev_data) {
+ *  switch (ev) {
+ *    case MG_EV_CONNECT:
+ *      mg_set_timer(c, 0);  // Clear connect timer
+ *      break;
+ *    case MG_EV_TIMER:
+ *      log("Connect timeout");
+ *      c->flags |= MG_F_CLOSE_IMMEDIATELY;
+ *      break;
+```
+ *
+ * NOTE: sub-second precision is not implemented yet, current granularity
+ * is 1 second.
+ */
+double mg_set_timer(struct mg_connection *c, double timestamp);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
@@ -2556,7 +2585,15 @@ void mg_dns_send_reply(struct mg_connection *, struct mg_dns_reply *);
 extern "C" {
 #endif /* __cplusplus */
 
-typedef void (*mg_resolve_callback_t)(struct mg_dns_message *, void *);
+enum mg_resolve_err {
+  MG_RESOLVE_OK = 0,
+  MG_RESOLVE_NO_ANSWERS = 1,
+  MG_RESOLVE_EXCEEDED_RETRY_COUNT = 2,
+  MG_RESOLVE_TIMEOUT = 3
+};
+
+typedef void (*mg_resolve_callback_t)(struct mg_dns_message *dns_message,
+                                      void *user_data, enum mg_resolve_err);
 
 /* Options for `mg_resolve_async_opt`. */
 struct mg_resolve_async_opts {
@@ -2565,6 +2602,7 @@ struct mg_resolve_async_opts {
   int timeout;        /* in seconds; defaults to 5 if zero */
   int accept_literal; /* pseudo-resolve literal ipv4 and ipv6 addrs */
   int only_literal;   /* only resolves literal addrs; sync cb invocation */
+  struct mg_connection **dns_conn; /* return DNS connection */
 };
 
 /* See `mg_resolve_async_opt()` */