diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3
index 35c183d7b0..32313499c3 100644
--- a/docs/libcurl/curl_easy_setopt.3
+++ b/docs/libcurl/curl_easy_setopt.3
@@ -199,6 +199,8 @@ Bind connection locally to port range. See \fICURLOPT_LOCALPORTRANGE(3)\fP
 Timeout for DNS cache. See \fICURLOPT_DNS_CACHE_TIMEOUT(3)\fP
 .IP CURLOPT_DNS_USE_GLOBAL_CACHE
 OBSOLETE Enable global DNS cache. See \fICURLOPT_DNS_USE_GLOBAL_CACHE(3)\fP
+.IP CURLOPT_DOH_URL
+Use this DOH server for name resolves. See \fICURLOPT_DOH_URL(3)\fP
 .IP CURLOPT_BUFFERSIZE
 Ask for alternate buffer size. See \fICURLOPT_BUFFERSIZE(3)\fP
 .IP CURLOPT_PORT
diff --git a/docs/libcurl/opts/CURLOPT_DOH_URL.3 b/docs/libcurl/opts/CURLOPT_DOH_URL.3
new file mode 100644
index 0000000000..407b92e20a
--- /dev/null
+++ b/docs/libcurl/opts/CURLOPT_DOH_URL.3
@@ -0,0 +1,71 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at https://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLOPT_DOH_URL 3 "18 Jun 2018" "libcurl 7.62.0" "curl_easy_setopt options"
+.SH NAME
+CURLOPT_DOH_URL \- provide the DNS-over-HTTPS URL
+.SH SYNOPSIS
+#include <curl/curl.h>
+
+CURLcode curl_easy_setopt(CURL *handle, CURLOPT_DOH_URL, char *URL);
+.SH DESCRIPTION
+Pass in a pointer to a \fIURL\fP for the DOH server to use for name
+resolving. The parameter should be a char * to a zero terminated string which
+must be URL-encoded in the following format: "https://host:port/path". It MUST
+specify a HTTPS URL.
+
+libcurl doesn't validate the syntax or use this variable until the transfer is
+issued. Even if you set a crazy value here, \fIcurl_easy_setopt(3)\fP will
+still return \fICURLE_OK\fP.
+
+curl sends POST requests to the given DNS-over-HTTPS URL.
+
+To find the DOH server itself, which might be specified using a name, libcurl
+will use the default name lookup function. You can bootstrap that by providing
+the address for the DOH server with \fICURLOPT_RESOLVE(3)\fP.
+
+Disable DOH use again by setting this option to NULL.
+.SH DEFAULT
+NULL - there is no default DOH URL. If this option isn't set, libcurl will use
+the default name resolver.
+.SH PROTOCOLS
+All
+.SH EXAMPLE
+.nf
+CURL *curl = curl_easy_init();
+if(curl) {
+  curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");
+  curl_easy_setopt(curl, CURLOPT_DOH_URL, "https://dns.example.com");
+  curl_easy_perform(curl);
+}
+.fi
+.SH AVAILABILITY
+Added in 7.62.0
+.SH RETURN VALUE
+Returns CURLE_OK on success or CURLE_OUT_OF_MEMORY if there was insufficient
+heap space.
+
+Note that \fIcurl_easy_setopt(3)\fP won't actually parse the given string so
+given a bad DOH URL, curl will not detect a problem until it tries to resolve
+a name with it.
+.SH "SEE ALSO"
+.BR CURLOPT_VERBOSE "(3), " CURLOPT_RESOLVE "(3), "
diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc
index 32485e2923..d65a82073c 100644
--- a/docs/libcurl/opts/Makefile.inc
+++ b/docs/libcurl/opts/Makefile.inc
@@ -122,6 +122,7 @@ man_MANS =                                      \
   CURLOPT_DNS_SERVERS.3                         \
   CURLOPT_DNS_SHUFFLE_ADDRESSES.3               \
   CURLOPT_DNS_USE_GLOBAL_CACHE.3                \
+  CURLOPT_DOH_URL.3                             \
   CURLOPT_EGDSOCKET.3                           \
   CURLOPT_ERRORBUFFER.3                         \
   CURLOPT_EXPECT_100_TIMEOUT_MS.3               \
diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions
index 96fdb7f443..108c3be326 100644
--- a/docs/libcurl/symbols-in-versions
+++ b/docs/libcurl/symbols-in-versions
@@ -384,6 +384,7 @@ CURLOPT_DNS_LOCAL_IP6           7.33.0
 CURLOPT_DNS_SERVERS             7.24.0
 CURLOPT_DNS_SHUFFLE_ADDRESSES   7.60.0
 CURLOPT_DNS_USE_GLOBAL_CACHE    7.9.3         7.11.1
+CURLOPT_DOH_URL                 7.62.0
 CURLOPT_EGDSOCKET               7.7
 CURLOPT_ENCODING                7.10
 CURLOPT_ERRORBUFFER             7.1
diff --git a/include/curl/curl.h b/include/curl/curl.h
index 767cb3b170..627cf5446d 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -1859,6 +1859,9 @@ typedef enum {
   /* Disallow specifying username/login in URL. */
   CINIT(DISALLOW_USERNAME_IN_URL, LONG, 278),
 
+  /* DNS-over-HTTPS URL */
+  CINIT(DOH_URL, STRINGPOINT, 279),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 
diff --git a/include/curl/typecheck-gcc.h b/include/curl/typecheck-gcc.h
index a6f6386140..244336204d 100644
--- a/include/curl/typecheck-gcc.h
+++ b/include/curl/typecheck-gcc.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -269,6 +269,7 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_off_t,
    (option) == CURLOPT_DNS_LOCAL_IP4 ||                                       \
    (option) == CURLOPT_DNS_LOCAL_IP6 ||                                       \
    (option) == CURLOPT_DNS_SERVERS ||                                         \
+   (option) == CURLOPT_DOH_URL ||                                             \
    (option) == CURLOPT_EGDSOCKET ||                                           \
    (option) == CURLOPT_FTPPORT ||                                             \
    (option) == CURLOPT_FTP_ACCOUNT ||                                         \
@@ -500,7 +501,8 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_off_t,
 /* evaluates to true if expr can be passed as POST data (void* or char*) */
 #define _curl_is_postfields(expr)                                             \
   (_curl_is_ptr((expr), void) ||                                              \
-   _curl_is_arr((expr), char))
+   _curl_is_arr((expr), char) ||                                              \
+   _curl_is_arr((expr), unsigned char))
 
 /* FIXME: the whole callback checking is messy...
  * The idea is to tolerate char vs. void and const vs. not const
diff --git a/lib/Makefile.inc b/lib/Makefile.inc
index 76ca6d0414..ac0affeaef 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -54,7 +54,8 @@ LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c   \
   http_ntlm.c curl_ntlm_wb.c curl_ntlm_core.c curl_sasl.c rand.c        \
   curl_multibyte.c hostcheck.c conncache.c pipeline.c dotdot.c          \
   x509asn1.c http2.c smb.c curl_endian.c curl_des.c system_win32.c      \
-  mime.c sha256.c setopt.c curl_path.c curl_ctype.c curl_range.c psl.c
+  mime.c sha256.c setopt.c curl_path.c curl_ctype.c curl_range.c psl.c  \
+  doh.c
 
 LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
   formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h if2ip.h         \
@@ -74,7 +75,7 @@ LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
   curl_setup_once.h multihandle.h setup-vms.h pipeline.h dotdot.h       \
   x509asn1.h http2.h sigpipe.h smb.h curl_endian.h curl_des.h           \
   curl_printf.h system_win32.h rand.h mime.h curl_sha256.h setopt.h     \
-  curl_path.h curl_ctype.h curl_range.h psl.h
+  curl_path.h curl_ctype.h curl_range.h psl.h doh.h
 
 LIB_RCFILES = libcurl.rc
 
diff --git a/lib/doh.c b/lib/doh.c
new file mode 100644
index 0000000000..5d1cd3b783
--- /dev/null
+++ b/lib/doh.c
@@ -0,0 +1,915 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+#include "urldata.h"
+#include "curl_addrinfo.h"
+#include "sendf.h"
+#include "multiif.h"
+#include "url.h"
+#include "share.h"
+#include "curl_base64.h"
+#include "connect.h"
+#include "doh.h"
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+#define DNS_CLASS_IN 0x01
+#define DOH_MAX_RESPONSE_SIZE 3000 /* bytes */
+
+typedef enum {
+  DNS_TYPE_A = 1,
+  DNS_TYPE_NS = 2,
+  DNS_TYPE_CNAME = 5,
+  DNS_TYPE_AAAA = 28
+} DNStype;
+
+#define MAX_ADDR 24
+
+typedef enum {
+  DOH_OK,
+  DOH_DNS_BAD_LABEL,    /* 1 */
+  DOH_DNS_OUT_OF_RANGE, /* 2 */
+  DOH_DNS_LABEL_LOOP,   /* 3 */
+  DOH_TOO_SMALL_BUFFER, /* 4 */
+  DOH_OUT_OF_MEM,       /* 5 */
+  DOH_DNS_RDATA_LEN,    /* 6 */
+  DOH_DNS_MALFORMAT,    /* 7 */
+  DOH_DNS_BAD_RCODE,    /* 8 - no such name */
+  DOH_DNS_UNEXPECTED_TYPE,  /* 9 */
+  DOH_DNS_UNEXPECTED_CLASS, /* 10 */
+  DOH_NO_CONTENT,           /* 11 */
+  DOH_DNS_BAD_ID            /* 12 */
+} DOHcode;
+
+static const char * const errors[]={
+  "",
+  "Bad label",
+  "Out of range",
+  "Label loop",
+  "Too small",
+  "Out of memory",
+  "RDATA length",
+  "Malformat",
+  "Bad RCODE",
+  "Unexpected TYPE",
+  "Unexpected CLASS",
+  "No content",
+  "Bad ID"
+};
+
+static const char *doh_strerror(DOHcode code)
+{
+  if((code >= DOH_OK) && (code <= DOH_DNS_BAD_ID))
+    return errors[code];
+  return "bad error code";
+}
+
+static DOHcode doh_encode(const char *host,
+                          DNStype dnstype,
+                          unsigned char *dnsp, /* buffer */
+                          size_t len,  /* buffer size */
+                          size_t *olen) /* output length */
+{
+  size_t hostlen = strlen(host);
+  unsigned char *orig = dnsp;
+  const char *hostp = host;
+
+  if(len < (12 + hostlen + 4))
+    return DOH_TOO_SMALL_BUFFER;
+
+  *dnsp++ = 0; /* 16 bit id */
+  *dnsp++ = 0;
+  *dnsp++ = 0x01; /* |QR|   Opcode  |AA|TC|RD| Set the RD bit */
+  *dnsp++ = '\0'; /* |RA|   Z    |   RCODE   |                */
+  *dnsp++ = '\0';
+  *dnsp++ = 1;    /* QDCOUNT (number of entries in the question section) */
+  *dnsp++ = '\0';
+  *dnsp++ = '\0'; /* ANCOUNT */
+  *dnsp++ = '\0';
+  *dnsp++ = '\0'; /* NSCOUNT */
+  *dnsp++ = '\0';
+  *dnsp++ = '\0'; /* ARCOUNT */
+
+  /* store a QNAME */
+  do {
+    char *dot = strchr(hostp, '.');
+    size_t labellen;
+    bool found = false;
+    if(dot) {
+      found = true;
+      labellen = dot - hostp;
+    }
+    else
+      labellen = strlen(hostp);
+    if(labellen > 63)
+      /* too long label, error out */
+      return DOH_DNS_BAD_LABEL;
+    *dnsp++ = (unsigned char)labellen;
+    memcpy(dnsp, hostp, labellen);
+    dnsp += labellen;
+    hostp += labellen + 1;
+    if(!found) {
+      *dnsp++ = 0; /* terminating zero */
+      break;
+    }
+  } while(1);
+
+  *dnsp++ = '\0'; /* upper 8 bit TYPE */
+  *dnsp++ = (unsigned char)dnstype;
+  *dnsp++ = '\0'; /* upper 8 bit CLASS */
+  *dnsp++ = DNS_CLASS_IN; /* IN - "the Internet" */
+
+  *olen = dnsp - orig;
+  return DOH_OK;
+}
+
+static size_t
+doh_write_cb(void *contents, size_t size, size_t nmemb, void *userp)
+{
+  size_t realsize = size * nmemb;
+  struct dohresponse *mem = (struct dohresponse *)userp;
+
+  if((mem->size + realsize) > DOH_MAX_RESPONSE_SIZE)
+    /* suspiciously much for us */
+    return 0;
+
+  mem->memory = realloc(mem->memory, mem->size + realsize);
+  if(mem->memory == NULL)
+    /* out of memory! */
+    return 0;
+
+  memcpy(&(mem->memory[mem->size]), contents, realsize);
+  mem->size += realsize;
+
+  return realsize;
+}
+
+/* called from multi.c when this DOH transfer is complete */
+static int Curl_doh_done(struct Curl_easy *doh, CURLcode result)
+{
+  struct Curl_easy *data = doh->set.dohfor;
+  /* so one of the DOH request done for the 'data' transfer is now complete! */
+  data->req.doh.pending--;
+  infof(data, "a DOH request is completed, %d to go\n", data->req.doh.pending);
+  if(result)
+    infof(data, "DOH request %s\n", curl_easy_strerror(result));
+
+  if(!data->req.doh.pending) {
+    /* DOH completed */
+    curl_slist_free_all(data->req.doh.headers);
+    data->req.doh.headers = NULL;
+    Curl_expire(data, 0, EXPIRE_RUN_NOW);
+  }
+  return 0;
+}
+
+#define ERROR_CHECK_SETOPT(x,y) result = curl_easy_setopt(doh, x, y);   \
+  if(result) goto error
+
+static CURLcode dohprobe(struct Curl_easy *data,
+                         struct dnsprobe *p, DNStype dnstype,
+                         const char *host,
+                         const char *url, CURLM *multi,
+                         struct curl_slist *headers)
+{
+  struct Curl_easy *doh = NULL;
+  char *nurl = NULL;
+  CURLcode result = CURLE_OK;
+  timediff_t timeout_ms;
+  DOHcode d = doh_encode(host, dnstype, p->dohbuffer, sizeof(p->dohbuffer),
+                         &p->dohlen);
+  if(d) {
+    failf(data, "Failed to encode DOH packet [%d]\n", d);
+    return CURLE_OUT_OF_MEMORY;
+  }
+
+  p->dnstype = dnstype;
+  p->serverdoh.memory = NULL;
+  /* the memory will be grown as needed by realloc in the doh_write_cb
+     function */
+  p->serverdoh.size = 0;
+
+  /* Note: this is code for sending the DoH request with GET but there's still
+     no logic that actually enables this. We should either add that ability or
+     yank out the GET code. Discuss! */
+  if(data->set.doh_get) {
+    char *b64;
+    size_t b64len;
+    result = Curl_base64url_encode(data, (char *)p->dohbuffer, p->dohlen,
+                                   &b64, &b64len);
+    if(result)
+      goto error;
+    nurl = aprintf("%s?dns=%s", url, b64);
+    free(b64);
+    if(!nurl) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto error;
+    }
+    url = nurl;
+  }
+
+  timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+  /* Curl_open() is the internal version of curl_easy_init() */
+  result = Curl_open(&doh);
+  if(!result) {
+    ERROR_CHECK_SETOPT(CURLOPT_URL, url);
+    ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_write_cb);
+    ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, (void *)&p->serverdoh);
+    if(!data->set.doh_get) {
+      ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, &p->dohbuffer[0]);
+      ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, p->dohlen);
+    }
+    ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, headers);
+    ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
+#ifndef CURLDEBUG
+    /* enforce HTTPS if not debug */
+    ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
+#endif
+    ERROR_CHECK_SETOPT(CURLOPT_TIMEOUT_MS, (long)timeout_ms);
+    ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L);
+    doh->set.fmultidone = Curl_doh_done;
+    doh->set.dohfor = data; /* identify for which transfer this is done */
+    p->easy = doh;
+
+    /* add this transfer to the multi handle */
+    if(curl_multi_add_handle(multi, doh))
+      goto error;
+  }
+  else
+    goto error;
+  free(nurl);
+  return CURLE_OK;
+
+  error:
+  free(nurl);
+  Curl_close(doh);
+  return result;
+}
+
+/*
+ * Curl_doh() resolves a name using DOH. It resolves a name and returns a
+ * 'Curl_addrinfo *' with the address information.
+ */
+
+Curl_addrinfo *Curl_doh(struct connectdata *conn,
+                        const char *hostname,
+                        int port,
+                        int *waitp)
+{
+  struct Curl_easy *data = conn->data;
+  CURLcode result = CURLE_OK;
+  *waitp = TRUE; /* this never returns synchronously */
+  (void)conn;
+  (void)hostname;
+  (void)port;
+
+  /* start clean, consider allocating this struct on demand */
+  memset(&data->req.doh, 0, sizeof(struct dohdata));
+
+  data->req.doh.host = hostname;
+  data->req.doh.port = port;
+  data->req.doh.headers =
+    curl_slist_append(NULL,
+                      "Content-Type: application/dns-message");
+  if(!data->req.doh.headers)
+    goto error;
+
+  if(conn->ip_version != CURL_IPRESOLVE_V6) {
+    /* create IPv4 DOH request */
+    result = dohprobe(data, &data->req.doh.probe[0], DNS_TYPE_A,
+                      hostname, data->set.str[STRING_DOH],
+                      data->multi, data->req.doh.headers);
+    if(result)
+      goto error;
+    data->req.doh.pending++;
+  }
+
+  if(conn->ip_version != CURL_IPRESOLVE_V4) {
+    /* create IPv6 DOH request */
+    result = dohprobe(data, &data->req.doh.probe[1], DNS_TYPE_AAAA,
+                      hostname, data->set.str[STRING_DOH],
+                      data->multi, data->req.doh.headers);
+    if(result)
+      goto error;
+    data->req.doh.pending++;
+  }
+  return NULL;
+
+  error:
+  curl_slist_free_all(data->req.doh.headers);
+  data->req.doh.headers = NULL;
+  curl_easy_cleanup(data->req.doh.probe[0].easy);
+  data->req.doh.probe[0].easy = NULL;
+  curl_easy_cleanup(data->req.doh.probe[1].easy);
+  data->req.doh.probe[1].easy = NULL;
+  return NULL;
+}
+
+static DOHcode skipqname(unsigned char *doh, size_t dohlen,
+                         unsigned int *indexp)
+{
+  unsigned char length;
+  do {
+    if(dohlen < (*indexp + 1))
+      return DOH_DNS_OUT_OF_RANGE;
+    length = doh[*indexp];
+    if((length & 0xc0) == 0xc0) {
+      /* name pointer, advance over it and be done */
+      if(dohlen < (*indexp + 2))
+        return DOH_DNS_OUT_OF_RANGE;
+      *indexp += 2;
+      break;
+    }
+    if(length & 0xc0)
+      return DOH_DNS_BAD_LABEL;
+    if(dohlen < (*indexp + 1 + length))
+      return DOH_DNS_OUT_OF_RANGE;
+    *indexp += 1 + length;
+  } while(length);
+  return DOH_OK;
+}
+
+static unsigned short get16bit(unsigned char *doh, int index)
+{
+  return (unsigned short)((doh[index] << 8) | doh[index + 1]);
+}
+
+static unsigned int get32bit(unsigned char *doh, int index)
+{
+  return (doh[index] << 24) | (doh[index + 1] << 16) |
+    (doh[index + 2] << 8) | doh[index + 3];
+}
+
+struct addr6 {
+  unsigned char byte[16];
+};
+
+struct cnamestore {
+  size_t len;       /* length of cname */
+  char *alloc;      /* allocated pointer */
+  size_t allocsize; /* allocated size */
+};
+
+struct dohaddr {
+  int type;
+  union {
+    unsigned int v4;
+    struct addr6 v6;
+  } ip;
+};
+
+struct dohentry {
+  unsigned int ttl;
+  int numaddr;
+  struct dohaddr addr[MAX_ADDR];
+  int numcname;
+  struct cnamestore cname[MAX_ADDR];
+};
+
+static DOHcode store_a(unsigned char *doh, int index, struct dohentry *d)
+{
+  /* silently ignore addresses over the limit */
+  if(d->numaddr < MAX_ADDR) {
+    struct dohaddr *a = &d->addr[d->numaddr];
+    a->type = DNS_TYPE_A;
+    a->ip.v4 = ntohl(get32bit(doh, index));
+    d->numaddr++;
+  }
+  return DOH_OK;
+}
+
+static DOHcode store_aaaa(unsigned char *doh, int index, struct dohentry *d)
+{
+  /* silently ignore addresses over the limit */
+  if(d->numaddr < MAX_ADDR) {
+    struct dohaddr *a = &d->addr[d->numaddr];
+    struct addr6 *inet6p = &a->ip.v6;
+    a->type = DNS_TYPE_AAAA;
+    memcpy(inet6p, &doh[index], 16);
+    d->numaddr++;
+  }
+  return DOH_OK;
+}
+
+static DOHcode cnameappend(struct cnamestore *c,
+                           unsigned char *src,
+                           size_t len)
+{
+  if(!c->alloc) {
+    c->allocsize = len + 1;
+    c->alloc = malloc(c->allocsize);
+    if(!c->alloc)
+      return DOH_OUT_OF_MEM;
+  }
+  else if(c->allocsize < (c->allocsize + len + 1)) {
+    char *ptr;
+    c->allocsize += len + 1;
+    ptr = realloc(c->alloc, c->allocsize);
+    if(!ptr) {
+      free(c->alloc);
+      return DOH_OUT_OF_MEM;
+    }
+    c->alloc = ptr;
+  }
+  memcpy(&c->alloc[c->len], src, len);
+  c->len += len;
+  c->alloc[c->len] = 0; /* keep it zero terminated */
+  return DOH_OK;
+}
+
+static DOHcode store_cname(unsigned char *doh,
+                           size_t dohlen,
+                           unsigned int index,
+                           struct dohentry *d)
+{
+  struct cnamestore *c = &d->cname[d->numcname++];
+  unsigned int loop = 128; /* a valid DNS name can never loop this much */
+  unsigned char length;
+  do {
+    if(index >= dohlen)
+      return DOH_DNS_OUT_OF_RANGE;
+    length = doh[index];
+    if((length & 0xc0) == 0xc0) {
+      int newpos;
+      /* name pointer, get the new offset (14 bits) */
+      if((index + 1) >= dohlen)
+        return DOH_DNS_OUT_OF_RANGE;
+
+      /* move to the the new index */
+      newpos = (length & 0x3f) << 8 | doh[index + 1];
+      index = newpos;
+      continue;
+    }
+    else if(length & 0xc0)
+      return DOH_DNS_BAD_LABEL; /* bad input */
+    else
+      index++;
+
+    if(length) {
+      DOHcode rc;
+      if(c->len) {
+        rc = cnameappend(c, (unsigned char *)".", 1);
+        if(rc)
+          return rc;
+      }
+      if((index + length) > dohlen)
+        return DOH_DNS_BAD_LABEL;
+
+      rc = cnameappend(c, &doh[index], length);
+      if(rc)
+        return rc;
+      index += length;
+    }
+  } while(length && --loop);
+
+  if(!loop)
+    return DOH_DNS_LABEL_LOOP;
+  return DOH_OK;
+}
+
+static DOHcode rdata(unsigned char *doh,
+                     size_t dohlen,
+                     unsigned short rdlength,
+                     unsigned short type,
+                     int index,
+                     struct dohentry *d)
+{
+  /* RDATA
+     - A (TYPE 1):  4 bytes
+     - AAAA (TYPE 28): 16 bytes
+     - NS (TYPE 2): N bytes */
+  DOHcode rc;
+
+  switch(type) {
+  case DNS_TYPE_A:
+    if(rdlength != 4)
+      return DOH_DNS_RDATA_LEN;
+    rc = store_a(doh, index, d);
+    if(rc)
+      return rc;
+    break;
+  case DNS_TYPE_AAAA:
+    if(rdlength != 16)
+      return DOH_DNS_RDATA_LEN;
+    rc = store_aaaa(doh, index, d);
+    if(rc)
+      return rc;
+    break;
+  case DNS_TYPE_CNAME:
+    rc = store_cname(doh, dohlen, index, d);
+    if(rc)
+      return rc;
+    break;
+  default:
+    /* unsupported type, just skip it */
+    break;
+  }
+  return DOH_OK;
+}
+
+static DOHcode doh_decode(unsigned char *doh,
+                          size_t dohlen,
+                          DNStype dnstype,
+                          struct dohentry *d)
+{
+  unsigned char rcode;
+  unsigned short qdcount;
+  unsigned short ancount;
+  unsigned short type = 0;
+  unsigned short class;
+  unsigned short rdlength;
+  unsigned short nscount;
+  unsigned short arcount;
+  unsigned int index = 12;
+  DOHcode rc;
+
+  d->ttl = INT_MAX;
+
+  if(dohlen < 12)
+    return DOH_TOO_SMALL_BUFFER; /* too small */
+  if(doh[0] || doh[1])
+    return DOH_DNS_BAD_ID; /* bad ID */
+  rcode = doh[3] & 0x0f;
+  if(rcode)
+    return DOH_DNS_BAD_RCODE; /* bad rcode */
+
+  qdcount = get16bit(doh, 4);
+  while(qdcount) {
+    rc = skipqname(doh, dohlen, &index);
+    if(rc)
+      return rc; /* bad qname */
+    if(dohlen < (index + 4))
+      return DOH_DNS_OUT_OF_RANGE;
+    index += 4; /* skip question's type and class */
+    qdcount--;
+  }
+
+  ancount = get16bit(doh, 6);
+  while(ancount) {
+    unsigned int ttl;
+
+    rc = skipqname(doh, dohlen, &index);
+    if(rc)
+      return rc; /* bad qname */
+
+    if(dohlen < (index + 2))
+      return DOH_DNS_OUT_OF_RANGE;
+
+    type = get16bit(doh, index);
+    if((type != DNS_TYPE_CNAME) && (type != dnstype))
+      /* Not the same type as was asked for nor CNAME */
+      return DOH_DNS_UNEXPECTED_TYPE;
+    index += 2;
+
+    if(dohlen < (index + 2))
+      return DOH_DNS_OUT_OF_RANGE;
+    class = get16bit(doh, index);
+    if(DNS_CLASS_IN != class)
+      return DOH_DNS_UNEXPECTED_CLASS; /* unsupported */
+    index += 2;
+
+    if(dohlen < (index + 4))
+      return DOH_DNS_OUT_OF_RANGE;
+
+    ttl = get32bit(doh, index);
+    if(ttl < d->ttl)
+      d->ttl = ttl;
+    index += 4;
+
+    if(dohlen < (index + 2))
+      return DOH_DNS_OUT_OF_RANGE;
+
+    rdlength = get16bit(doh, index);
+    index += 2;
+    if(dohlen < (index + rdlength))
+      return DOH_DNS_OUT_OF_RANGE;
+
+    rc = rdata(doh, dohlen, rdlength, type, index, d);
+    if(rc)
+      return rc; /* bad rdata */
+    index += rdlength;
+    ancount--;
+  }
+
+  nscount = get16bit(doh, 8);
+  while(nscount) {
+    rc = skipqname(doh, dohlen, &index);
+    if(rc)
+      return rc; /* bad qname */
+
+    if(dohlen < (index + 8))
+      return DOH_DNS_OUT_OF_RANGE;
+
+    index += 2 + 2 + 4; /* type, class and ttl */
+
+    if(dohlen < (index + 2))
+      return DOH_DNS_OUT_OF_RANGE;
+
+    rdlength = get16bit(doh, index);
+    index += 2;
+    if(dohlen < (index + rdlength))
+      return DOH_DNS_OUT_OF_RANGE;
+    index += rdlength;
+    nscount--;
+  }
+
+  arcount = get16bit(doh, 10);
+  while(arcount) {
+    rc = skipqname(doh, dohlen, &index);
+    if(rc)
+      return rc; /* bad qname */
+
+    if(dohlen < (index + 8))
+      return DOH_DNS_OUT_OF_RANGE;
+
+    index += 2 + 2 + 4; /* type, class and ttl */
+
+    if(dohlen < (index + 2))
+      return DOH_DNS_OUT_OF_RANGE;
+
+    rdlength = get16bit(doh, index);
+    index += 2;
+    if(dohlen < (index + rdlength))
+      return DOH_DNS_OUT_OF_RANGE;
+    index += rdlength;
+    arcount--;
+  }
+
+  if(index != dohlen)
+    return DOH_DNS_MALFORMAT; /* something is wrong */
+
+  if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr)
+    /* nothing stored! */
+    return DOH_NO_CONTENT;
+
+  return DOH_OK; /* ok */
+}
+
+static void showdoh(struct Curl_easy *data,
+                    struct dohentry *d)
+{
+  int i;
+  infof(data, "TTL: %u seconds\n", d->ttl);
+  for(i = 0; i < d->numaddr; i++) {
+    struct dohaddr *a = &d->addr[i];
+    if(a->type == DNS_TYPE_A) {
+      infof(data, "DOH A: %d.%d.%d.%d\n",
+            a->ip.v4 & 0xff, (a->ip.v4>>8) & 0xff,
+            (a->ip.v4>>16) & 0xff, a->ip.v4 >>24);
+    }
+    else if(a->type == DNS_TYPE_AAAA) {
+      int j;
+      char buffer[128];
+      char *ptr;
+      size_t len;
+      snprintf(buffer, 128, "DOH AAAA: ");
+      ptr = &buffer[10];
+      len = 118;
+      for(j = 0; j < 16; j += 2) {
+        size_t l;
+        snprintf(ptr, len, "%s%02x%02x", j?":":"", d->addr[i].ip.v6.byte[j],
+                 d->addr[i].ip.v6.byte[j + 1]);
+        l = strlen(ptr);
+        len -= l;
+        ptr += l;
+      }
+      infof(data, "%s\n", buffer);
+    }
+  }
+  for(i = 0; i < d->numcname; i++) {
+    infof(data, "CNAME: %s\n", d->cname[i].alloc);
+  }
+}
+
+/*
+ * doh2ai()
+ *
+ * This function returns a pointer to the first element of a newly allocated
+ * Curl_addrinfo struct linked list filled with the data from a set of DOH
+ * lookups.  Curl_addrinfo is meant to work like the addrinfo struct does for
+ * a IPv6 stack, but usable also for IPv4, all hosts and environments.
+ *
+ * The memory allocated by this function *MUST* be free'd later on calling
+ * Curl_freeaddrinfo().  For each successful call to this function there
+ * must be an associated call later to Curl_freeaddrinfo().
+ */
+
+static Curl_addrinfo *
+doh2ai(const struct dohentry *de, const char *hostname, int port)
+{
+  Curl_addrinfo *ai;
+  Curl_addrinfo *prevai = NULL;
+  Curl_addrinfo *firstai = NULL;
+  struct sockaddr_in *addr;
+#ifdef ENABLE_IPV6
+  struct sockaddr_in6 *addr6;
+#endif
+  CURLcode result = CURLE_OK;
+  int i;
+
+  if(!de)
+    /* no input == no output! */
+    return NULL;
+
+  for(i = 0; i < de->numaddr; i++) {
+    size_t ss_size;
+    CURL_SA_FAMILY_T addrtype;
+    if(de->addr[i].type == DNS_TYPE_AAAA) {
+#ifndef ENABLE_IPV6
+      /* we can't handle IPv6 addresses */
+      continue;
+#else
+      ss_size = sizeof(struct sockaddr_in6);
+      addrtype = AF_INET6;
+#endif
+    }
+    else {
+      ss_size = sizeof(struct sockaddr_in);
+      addrtype = AF_INET;
+    }
+
+    ai = calloc(1, sizeof(Curl_addrinfo));
+    if(!ai) {
+      result = CURLE_OUT_OF_MEMORY;
+      break;
+    }
+    ai->ai_canonname = strdup(hostname);
+    if(!ai->ai_canonname) {
+      result = CURLE_OUT_OF_MEMORY;
+      free(ai);
+      break;
+    }
+    ai->ai_addr = calloc(1, ss_size);
+    if(!ai->ai_addr) {
+      result = CURLE_OUT_OF_MEMORY;
+      free(ai->ai_canonname);
+      free(ai);
+      break;
+    }
+
+    if(!firstai)
+      /* store the pointer we want to return from this function */
+      firstai = ai;
+
+    if(prevai)
+      /* make the previous entry point to this */
+      prevai->ai_next = ai;
+
+    ai->ai_family = addrtype;
+
+    /* we return all names as STREAM, so when using this address for TFTP
+       the type must be ignored and conn->socktype be used instead! */
+    ai->ai_socktype = SOCK_STREAM;
+
+    ai->ai_addrlen = (curl_socklen_t)ss_size;
+
+    /* leave the rest of the struct filled with zero */
+
+    switch(ai->ai_family) {
+    case AF_INET:
+      addr = (void *)ai->ai_addr; /* storage area for this info */
+
+      memcpy(&addr->sin_addr, &de->addr[i].ip.v4, sizeof(struct in_addr));
+      addr->sin_family = (CURL_SA_FAMILY_T)addrtype;
+      addr->sin_port = htons((unsigned short)port);
+      break;
+
+#ifdef ENABLE_IPV6
+    case AF_INET6:
+      addr6 = (void *)ai->ai_addr; /* storage area for this info */
+
+      memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr));
+      addr6->sin6_family = (CURL_SA_FAMILY_T)addrtype;
+      addr6->sin6_port = htons((unsigned short)port);
+      break;
+#endif
+    }
+
+    prevai = ai;
+  }
+
+  if(result) {
+    Curl_freeaddrinfo(firstai);
+    firstai = NULL;
+  }
+
+  return firstai;
+}
+
+static const char *type2name(DNStype dnstype)
+{
+  return (dnstype == DNS_TYPE_A)?"A":"AAAA";
+}
+
+static void de_cleanup(struct dohentry *d)
+{
+  int i = 0;
+  for(i = 0; i < d->numcname; i++) {
+    free(d->cname[i].alloc);
+  }
+}
+
+CURLcode Curl_doh_is_resolved(struct connectdata *conn,
+                              struct Curl_dns_entry **dnsp)
+{
+  struct Curl_easy *data = conn->data;
+  *dnsp = NULL; /* defaults to no response */
+
+  if(!data->req.doh.probe[0].easy && !data->req.doh.probe[1].easy) {
+    failf(data, "Could not DOH-resolve: %s", conn->async.hostname);
+    return conn->bits.proxy?CURLE_COULDNT_RESOLVE_PROXY:
+      CURLE_COULDNT_RESOLVE_HOST;
+  }
+  else if(!data->req.doh.pending) {
+    DOHcode rc;
+    DOHcode rc2;
+    struct dohentry de;
+    struct Curl_dns_entry *dns;
+    struct Curl_addrinfo *ai;
+    /* remove DOH handles from multi handle and close them */
+    curl_multi_remove_handle(data->multi, data->req.doh.probe[0].easy);
+    Curl_close(data->req.doh.probe[0].easy);
+    curl_multi_remove_handle(data->multi, data->req.doh.probe[1].easy);
+    Curl_close(data->req.doh.probe[1].easy);
+
+    /* parse the responses, create the struct and return it! */
+    memset(&de, 0, sizeof(de));
+    rc = doh_decode(data->req.doh.probe[0].serverdoh.memory,
+                    data->req.doh.probe[0].serverdoh.size,
+                    data->req.doh.probe[0].dnstype,
+                    &de);
+    free(data->req.doh.probe[0].serverdoh.memory);
+    if(rc) {
+      infof(data, "DOH: %s type %s for %s\n", doh_strerror(rc),
+            type2name(data->req.doh.probe[0].dnstype),
+            data->req.doh.host);
+    }
+    rc2 = doh_decode(data->req.doh.probe[1].serverdoh.memory,
+                     data->req.doh.probe[1].serverdoh.size,
+                     data->req.doh.probe[1].dnstype,
+                     &de);
+    free(data->req.doh.probe[1].serverdoh.memory);
+    if(rc2) {
+      infof(data, "DOG: %s type %s for %s\n", doh_strerror(rc2),
+            type2name(data->req.doh.probe[1].dnstype),
+            data->req.doh.host);
+    }
+    if(!rc || !rc2) {
+      infof(data, "DOH Host name: %s\n", data->req.doh.host);
+      showdoh(data, &de);
+
+      ai = doh2ai(&de, data->req.doh.host, data->req.doh.port);
+      if(!ai) {
+        de_cleanup(&de);
+        return CURLE_OUT_OF_MEMORY;
+      }
+
+      if(data->share)
+        Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
+
+      /* we got a response, store it in the cache */
+      dns = Curl_cache_addr(data, ai, data->req.doh.host, data->req.doh.port);
+
+      if(data->share)
+        Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
+
+      de_cleanup(&de);
+      if(!dns)
+        /* returned failure, bail out nicely */
+        Curl_freeaddrinfo(ai);
+      else {
+        conn->async.dns = dns;
+        *dnsp = dns;
+        return CURLE_OK;
+      }
+    }
+    de_cleanup(&de);
+
+    return CURLE_COULDNT_RESOLVE_HOST;
+  }
+
+  return CURLE_OK;
+}
diff --git a/lib/doh.h b/lib/doh.h
new file mode 100644
index 0000000000..e9c017c721
--- /dev/null
+++ b/lib/doh.h
@@ -0,0 +1,44 @@
+#ifndef HEADER_CURL_DOH_H
+#define HEADER_CURL_DOH_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "urldata.h"
+#include "curl_addrinfo.h"
+
+/*
+ * Curl_doh() resolve a name using DoH (DNS-over-HTTPS). It resolves a name
+ * and returns a 'Curl_addrinfo *' with the address information.
+ */
+
+Curl_addrinfo *Curl_doh(struct connectdata *conn,
+                        const char *hostname,
+                        int port,
+                        int *waitp);
+
+CURLcode Curl_doh_is_resolved(struct connectdata *conn,
+                              struct Curl_dns_entry **dns);
+
+int Curl_doh_getsock(struct connectdata *conn, curl_socket_t *socks,
+                     int numsocks);
+
+#endif
diff --git a/lib/hostasyn.c b/lib/hostasyn.c
index e7b399ed27..6ff60ba61a 100644
--- a/lib/hostasyn.c
+++ b/lib/hostasyn.c
@@ -111,31 +111,6 @@ CURLcode Curl_addrinfo_callback(struct connectdata *conn,
   return result;
 }
 
-/* Call this function after Curl_connect() has returned async=TRUE and
-   then a successful name resolve has been received.
-
-   Note: this function disconnects and frees the conn data in case of
-   resolve failure */
-CURLcode Curl_async_resolved(struct connectdata *conn,
-                             bool *protocol_done)
-{
-  CURLcode result;
-
-  if(conn->async.dns) {
-    conn->dns_entry = conn->async.dns;
-    conn->async.dns = NULL;
-  }
-
-  result = Curl_setup_conn(conn, protocol_done);
-
-  if(result)
-    /* We're not allowed to return failure with memory left allocated
-       in the connectdata struct, free those here */
-    Curl_disconnect(conn->data, conn, TRUE); /* close the connection */
-
-  return result;
-}
-
 /*
  * Curl_getaddrinfo() is the generic low-level name resolve API within this
  * source file. There are several versions of this function - for different
diff --git a/lib/hostip.c b/lib/hostip.c
index bc20f71538..8d46c1d828 100644
--- a/lib/hostip.c
+++ b/lib/hostip.c
@@ -60,6 +60,7 @@
 #include "url.h"
 #include "inet_ntop.h"
 #include "multiif.h"
+#include "doh.h"
 #include "warnless.h"
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
@@ -565,23 +566,27 @@ int Curl_resolv(struct connectdata *conn,
         return CURLRESOLV_ERROR;
     }
 
-    /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a
-       non-zero value indicating that we need to wait for the response to the
-       resolve call */
-    addr = Curl_getaddrinfo(conn,
+    if(data->set.doh) {
+      addr = Curl_doh(conn, hostname, port, &respwait);
+    }
+    else {
+      /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a
+         non-zero value indicating that we need to wait for the response to the
+         resolve call */
+      addr = Curl_getaddrinfo(conn,
 #ifdef DEBUGBUILD
-                            (data->set.str[STRING_DEVICE]
-                             && !strcmp(data->set.str[STRING_DEVICE],
-                                        "LocalHost"))?"localhost":
+                              (data->set.str[STRING_DEVICE]
+                               && !strcmp(data->set.str[STRING_DEVICE],
+                                          "LocalHost"))?"localhost":
 #endif
-                            hostname, port, &respwait);
-
+                              hostname, port, &respwait);
+    }
     if(!addr) {
       if(respwait) {
         /* the response to our resolve call will come asynchronously at
            a later time, good or bad */
         /* First, check that we haven't received the info by now */
-        result = Curl_resolver_is_resolved(conn, &dns);
+        result = Curl_resolv_check(conn, &dns);
         if(result) /* error detected */
           return CURLRESOLV_ERROR;
         if(dns)
@@ -1053,3 +1058,55 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data)
 
   return CURLE_OK;
 }
+
+CURLcode Curl_resolv_check(struct connectdata *conn,
+                           struct Curl_dns_entry **dns)
+{
+  if(conn->data->set.doh)
+    return Curl_doh_is_resolved(conn, dns);
+  return Curl_resolver_is_resolved(conn, dns);
+}
+
+int Curl_resolv_getsock(struct connectdata *conn,
+                        curl_socket_t *socks,
+                        int numsocks)
+{
+#ifdef CURLRES_ASYNCH
+  if(conn->data->set.doh)
+    /* nothing to wait for during DOH resolve, those handles have their own
+       sockets */
+    return GETSOCK_BLANK;
+  return Curl_resolver_getsock(conn, socks, numsocks);
+#else
+  (void)conn;
+  (void)socks;
+  (void)numsocks;
+  return GETSOCK_BLANK;
+#endif
+}
+
+/* Call this function after Curl_connect() has returned async=TRUE and
+   then a successful name resolve has been received.
+
+   Note: this function disconnects and frees the conn data in case of
+   resolve failure */
+CURLcode Curl_once_resolved(struct connectdata *conn,
+                            bool *protocol_done)
+{
+  CURLcode result;
+
+  if(conn->async.dns) {
+    conn->dns_entry = conn->async.dns;
+    conn->async.dns = NULL;
+  }
+
+  result = Curl_setup_conn(conn, protocol_done);
+
+  if(result)
+    /* We're not allowed to return failure with memory left allocated
+       in the connectdata struct, free those here */
+    Curl_disconnect(conn->data, conn, TRUE); /* close the connection */
+
+  return result;
+}
+
diff --git a/lib/hostip.h b/lib/hostip.h
index 1de4bee8d6..29fd1ef7cc 100644
--- a/lib/hostip.h
+++ b/lib/hostip.h
@@ -145,12 +145,7 @@ int curl_dogetnameinfo(GETNAMEINFO_QUAL_ARG1 GETNAMEINFO_TYPE_ARG1 sa,
 /* IPv4 threadsafe resolve function used for synch and asynch builds */
 Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port);
 
-CURLcode Curl_async_resolved(struct connectdata *conn,
-                             bool *protocol_connect);
-
-#ifndef CURLRES_ASYNCH
-#define Curl_async_resolved(x,y) CURLE_OK
-#endif
+CURLcode Curl_once_resolved(struct connectdata *conn, bool *protocol_connect);
 
 /*
  * Curl_addrinfo_callback() is used when we build with any asynch specialty.
@@ -258,4 +253,10 @@ void Curl_hostcache_destroy(struct Curl_easy *data);
  */
 CURLcode Curl_loadhostpairs(struct Curl_easy *data);
 
+CURLcode Curl_resolv_check(struct connectdata *conn,
+                           struct Curl_dns_entry **dns);
+int Curl_resolv_getsock(struct connectdata *conn,
+                        curl_socket_t *socks,
+                        int numsocks);
+
 #endif /* HEADER_CURL_HOSTIP_H */
diff --git a/lib/multi.c b/lib/multi.c
index 9d5afc0932..066adfed37 100644
--- a/lib/multi.c
+++ b/lib/multi.c
@@ -906,7 +906,7 @@ static int multi_getsock(struct Curl_easy *data,
     return 0;
 
   case CURLM_STATE_WAITRESOLVE:
-    return Curl_resolver_getsock(data->easy_conn, socks, numsocks);
+    return Curl_resolv_getsock(data->easy_conn, socks, numsocks);
 
   case CURLM_STATE_PROTOCONNECT:
   case CURLM_STATE_SENDPROTOCONNECT:
@@ -1236,7 +1236,7 @@ static CURLcode multi_reconnect_request(struct connectdata **connp)
           return result;
 
         /* Resolved, continue with the connection */
-        result = Curl_async_resolved(conn, &protocol_done);
+        result = Curl_once_resolved(conn, &protocol_done);
         if(result)
           return result;
       }
@@ -1512,7 +1512,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
       }
 
       if(!dns)
-        result = Curl_resolver_is_resolved(data->easy_conn, &dns);
+        result = Curl_resolv_check(data->easy_conn, &dns);
 
       /* Update sockets here, because the socket(s) may have been
          closed and the application thus needs to be told, even if it
@@ -1525,10 +1525,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
       if(dns) {
         /* Perform the next step in the connection phase, and then move on
            to the WAITCONNECT state */
-        result = Curl_async_resolved(data->easy_conn, &protocol_connect);
+        result = Curl_once_resolved(data->easy_conn, &protocol_connect);
 
         if(result)
-          /* if Curl_async_resolved() returns failure, the connection struct
+          /* if Curl_once_resolved() returns failure, the connection struct
              is already freed and gone */
           data->easy_conn = NULL;           /* no more connection */
         else {
@@ -2132,15 +2132,21 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
     }
 
     if(CURLM_STATE_COMPLETED == data->mstate) {
-      /* now fill in the Curl_message with this info */
-      msg = &data->msg;
+      if(data->set.fmultidone) {
+        /* signal via callback instead */
+        data->set.fmultidone(data, result);
+      }
+      else {
+        /* now fill in the Curl_message with this info */
+        msg = &data->msg;
 
-      msg->extmsg.msg = CURLMSG_DONE;
-      msg->extmsg.easy_handle = data;
-      msg->extmsg.data.result = result;
+        msg->extmsg.msg = CURLMSG_DONE;
+        msg->extmsg.easy_handle = data;
+        msg->extmsg.data.result = result;
 
-      rc = multi_addmsg(multi, msg);
-      DEBUGASSERT(!data->easy_conn);
+        rc = multi_addmsg(multi, msg);
+        DEBUGASSERT(!data->easy_conn);
+      }
       multistate(data, CURLM_STATE_MSGSENT);
     }
   } while((rc == CURLM_CALL_MULTI_PERFORM) || multi_ischanged(multi, FALSE));
diff --git a/lib/setopt.c b/lib/setopt.c
index 66636a2da2..475a337980 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -2603,6 +2603,11 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option,
     data->set.disallow_username_in_url =
       (0 != va_arg(param, long)) ? TRUE : FALSE;
     break;
+  case CURLOPT_DOH_URL:
+    result = Curl_setstropt(&data->set.str[STRING_DOH],
+                            va_arg(param, char *));
+    data->set.doh = data->set.str[STRING_DOH]?TRUE:FALSE;
+    break;
   default:
     /* unknown tag and its companion, just ignore: */
     result = CURLE_UNKNOWN_OPTION;
diff --git a/lib/urldata.h b/lib/urldata.h
index add77e09f7..769ab4fa4f 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -476,7 +476,6 @@ struct hostname {
 #define KEEP_SENDBITS (KEEP_SEND | KEEP_SEND_HOLD | KEEP_SEND_PAUSE)
 
 
-#ifdef CURLRES_ASYNCH
 struct Curl_async {
   char *hostname;
   int port;
@@ -485,7 +484,6 @@ struct Curl_async {
   int status; /* if done is TRUE, this is the status from the callback */
   void *os_specific;  /* 'struct thread_data' for Windows */
 };
-#endif
 
 #define FIRSTSOCKET     0
 #define SECONDARYSOCKET 1
@@ -511,6 +509,28 @@ enum upgrade101 {
   UPGR101_WORKING             /* talking upgraded protocol */
 };
 
+struct dohresponse {
+  unsigned char *memory;
+  size_t size;
+};
+
+/* one of these for each DoH request */
+struct dnsprobe {
+  CURL *easy;
+  int dnstype;
+  unsigned char dohbuffer[512];
+  size_t dohlen;
+  struct dohresponse serverdoh;
+};
+
+struct dohdata {
+  struct curl_slist *headers;
+  struct dnsprobe probe[2];
+  unsigned int pending; /* still outstanding requests */
+  const char *host;
+  int port;
+};
+
 /*
  * Request specific data in the easy handle (Curl_easy).  Previously,
  * these members were on the connectdata struct but since a conn struct may
@@ -606,6 +626,7 @@ struct SingleRequest {
 
   void *protop;       /* Allocated protocol-specific data. Each protocol
                          handler makes sure this points to data it needs. */
+  struct dohdata doh; /* DoH specific data for this request */
 };
 
 /*
@@ -969,11 +990,8 @@ struct connectdata {
 #endif
 
   char syserr_buf [256]; /* buffer for Curl_strerror() */
-
-#ifdef CURLRES_ASYNCH
   /* data used for the asynch name resolve callback */
   struct Curl_async async;
-#endif
 
   /* These three are used for chunked-encoding trailer support */
   char *trailer; /* allocated buffer to store trailer in */
@@ -1442,6 +1460,7 @@ enum dupstring {
   STRING_UNIX_SOCKET_PATH,      /* path to Unix socket, if used */
 #endif
   STRING_TARGET,                /* CURLOPT_REQUEST_TARGET */
+  STRING_DOH,                   /* CURLOPT_DOH_URL */
   /* -- end of zero-terminated strings -- */
 
   STRING_LASTZEROTERMINATED,
@@ -1453,6 +1472,11 @@ enum dupstring {
   STRING_LAST /* not used, just an end-of-list marker */
 };
 
+/* callback that gets called when this easy handle is completed within a multi
+   handle.  Only used for internally created transfers, like for example
+   DoH. */
+typedef int (*multidone_func)(struct Curl_easy *easy, CURLcode result);
+
 struct UserDefined {
   FILE *err;         /* the stderr user data goes here */
   void *debugdata;   /* the data that will be passed to fdebug */
@@ -1688,6 +1712,10 @@ struct UserDefined {
                                                   before resolver start */
   void *resolver_start_client; /* pointer to pass to resolver start callback */
   bool disallow_username_in_url; /* disallow username in url */
+  bool doh; /* DNS-over-HTTPS enabled */
+  bool doh_get; /* use GET for DoH requests, instead of POST */
+  multidone_func fmultidone;
+  struct Curl_easy *dohfor; /* this is a DoH request for that transfer */
 };
 
 struct Names {