diff --git a/src/dring/account_const.h b/src/dring/account_const.h
index 203385cfc784337e099e70de0c755116b834ff06..34415f620cf2c09fcf133d308df1372bf20f0609 100644
--- a/src/dring/account_const.h
+++ b/src/dring/account_const.h
@@ -147,6 +147,8 @@ constexpr static const char ACCOUNT_PEER_DISCOVERY  [] = "Account.accountDiscove
 constexpr static const char ACCOUNT_PUBLISH         [] = "Account.accountPublish";
 constexpr static const char MANAGER_URI             [] = "Account.managerUri";
 constexpr static const char MANAGER_USERNAME        [] = "Account.managerUsername";
+constexpr static const char BOOTSTRAP_LIST_URL      [] = "Account.bootstrapListUrl";
+constexpr static const char DHT_PROXY_LIST_URL      [] = "Account.dhtProxyListUrl";
 
 namespace Audio {
 
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index c23bd4fe4f1f14ffff0247dae7c53aac593ce9c0..2027cb69604384bdd3f15556f79ef70573ac085d 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -74,6 +74,8 @@
 
 #include <opendht/thread_pool.h>
 #include <opendht/peer_discovery.h>
+#include <opendht/http.h>
+
 #include <yaml-cpp/yaml.h>
 #include <json/json.h>
 
@@ -695,6 +697,7 @@ void JamiAccount::serialize(YAML::Emitter &out) const
     out << YAML::Key << Conf::PROXY_ENABLED_KEY << YAML::Value << proxyEnabled_;
     out << YAML::Key << Conf::PROXY_SERVER_KEY << YAML::Value << proxyServer_;
     out << YAML::Key << Conf::PROXY_PUSH_TOKEN_KEY << YAML::Value << deviceKey_;
+    out << YAML::Key << DRing::Account::ConfProperties::DHT_PROXY_LIST_URL << YAML::Value << proxyListUrl_;
 
 #if HAVE_RINGNS
     out << YAML::Key << DRing::Account::ConfProperties::RingNS::URI << YAML::Value <<  nameServer_;
@@ -743,6 +746,11 @@ void JamiAccount::unserialize(const YAML::Node &node)
     parseValue(node, Conf::PROXY_ENABLED_KEY, proxyEnabled_);
     parseValue(node, Conf::PROXY_SERVER_KEY, proxyServer_);
     parseValue(node, Conf::PROXY_PUSH_TOKEN_KEY, deviceKey_);
+    try {
+        parseValue(node, DRing::Account::ConfProperties::DHT_PROXY_LIST_URL, proxyListUrl_);
+    } catch (const std::exception& e) {
+        proxyListUrl_ = DHT_DEFAULT_PROXY_LIST_URL;
+    }
 
     parseValueOptional(node, DRing::Account::ConfProperties::RING_DEVICE_NAME, ringDeviceName_);
     parseValueOptional(node, DRing::Account::ConfProperties::MANAGER_URI, managerUri_);
@@ -1068,6 +1076,7 @@ JamiAccount::setAccountDetails(const std::map<std::string, std::string>& details
 
     if (hostname_.empty())
         hostname_ = DHT_DEFAULT_BOOTSTRAP;
+    parseString(details, DRing::Account::ConfProperties::BOOTSTRAP_LIST_URL, bootstrapListUrl_);
     parseInt(details, Conf::CONFIG_DHT_PORT, dhtPort_);
     parseBool(details, Conf::CONFIG_DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_);
     parseBool(details, DRing::Account::ConfProperties::DHT_PEER_DISCOVERY, dhtPeerDiscovery_);
@@ -1093,8 +1102,9 @@ JamiAccount::setAccountDetails(const std::map<std::string, std::string>& details
     parsePath(details, DRing::Account::ConfProperties::ARCHIVE_PATH,     archive_path, idPath_);
     parseString(details, DRing::Account::ConfProperties::RING_DEVICE_NAME, ringDeviceName_);
 
+    auto oldProxyServer = proxyServer_, oldProxyServerList = proxyListUrl_;
+    parseString(details, DRing::Account::ConfProperties::DHT_PROXY_LIST_URL, proxyListUrl_);
     parseBool(details, DRing::Account::ConfProperties::PROXY_ENABLED, proxyEnabled_);
-    auto oldProxyServer = proxyServer_;
     parseString(details, DRing::Account::ConfProperties::PROXY_SERVER, proxyServer_);
     parseString(details, DRing::Account::ConfProperties::PROXY_PUSH_TOKEN, deviceKey_);
     // Migrate from old versions
@@ -1103,11 +1113,13 @@ JamiAccount::setAccountDetails(const std::map<std::string, std::string>& details
     ||  proxyServer_ == "dhtproxy.ring.cx")
     &&  proxyServerCached_.empty()))
         proxyServer_ = DHT_DEFAULT_PROXY;
-    if (proxyServer_ != oldProxyServer) {
+    if (proxyServer_ != oldProxyServer || oldProxyServerList != proxyListUrl_) {
         JAMI_DBG("DHT Proxy configuration changed, resetting cache");
         proxyServerCached_ = {};
         auto proxyCachePath = cachePath_ + DIR_SEPARATOR_STR "dhtproxy";
+        auto proxyListCachePath = cachePath_ + DIR_SEPARATOR_STR "dhtproxylist";
         std::remove(proxyCachePath.c_str());
+        std::remove(proxyListCachePath.c_str());
     }
 
 #if HAVE_RINGNS
@@ -1165,6 +1177,7 @@ JamiAccount::getAccountDetails() const
     a.emplace(Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC,    "-1");
     a.emplace(DRing::Account::ConfProperties::PROXY_ENABLED,    proxyEnabled_ ? TRUE_STR : FALSE_STR);
     a.emplace(DRing::Account::ConfProperties::PROXY_SERVER,     proxyServer_);
+    a.emplace(DRing::Account::ConfProperties::DHT_PROXY_LIST_URL,     proxyListUrl_);
     a.emplace(DRing::Account::ConfProperties::PROXY_PUSH_TOKEN, deviceKey_);
     a.emplace(DRing::Account::ConfProperties::MANAGER_URI, managerUri_);
     a.emplace(DRing::Account::ConfProperties::MANAGER_USERNAME, managerUsername_);
@@ -1464,32 +1477,44 @@ JamiAccount::handlePendingCall(PendingCall& pc, bool incoming)
 }
 
 void
-JamiAccount::mapPortUPnP()
+JamiAccount::registerAsyncOps()
 {
-    upnp_->requestMappingAdd([this](uint16_t port_used, bool success) {
-        auto oldPort = static_cast<in_port_t>(dhtPortUsed_);
-        auto newPort = success ? port_used : dhtPort_;
-
-        if (not success and not dht_->isRunning()) {
-            JAMI_WARN("[Account %s] Failed to open port %u: starting DHT anyways", getAccountID().c_str(), oldPort);
+    auto onLoad = [this, loaded = std::make_shared<std::atomic_uint>()]{
+        if (++(*loaded) == 2u)
             doRegister_();
-            return;
-        }
+    };
+
+    loadCachedProxyServer([this, onLoad](const std::string& proxy) {
+        onLoad();
+    });
+
+    if (upnp_) {
+        upnp_->requestMappingAdd([this, onLoad](uint16_t port_used, bool success) {
+            auto oldPort = static_cast<in_port_t>(dhtPortUsed_);
+            auto newPort = success ? port_used : dhtPort_;
 
-        if (oldPort != newPort or not dht_->isRunning()){
-            dhtPortUsed_ = newPort;
-            if (not dht_->isRunning()) {
-                JAMI_WARN("[Account %s] Starting DHT on port %u", getAccountID().c_str(), newPort);
-                doRegister_();
+            if (not success and not dht_->isRunning()) {
+                JAMI_WARN("[Account %s] Failed to open port %u: starting DHT anyways", getAccountID().c_str(), oldPort);
+                onLoad();
+                return;
+            }
+
+            if (oldPort != newPort or not dht_->isRunning()){
+                dhtPortUsed_ = newPort;
+                if (not dht_->isRunning()) {
+                    JAMI_WARN("[Account %s] Starting DHT on port %u", getAccountID().c_str(), newPort);
+                    onLoad();
+                } else {
+                    JAMI_WARN("[Account %s] DHT port changed to %u: restarting network", getAccountID().c_str(), newPort);
+                    dht_->connectivityChanged();
+                }
             } else {
-                JAMI_WARN("[Account %s] DHT port changed to %u: restarting network", getAccountID().c_str(), newPort);
+                JAMI_WARN("[Account %s] DHT port %u opened: restarting network", getAccountID().c_str(), newPort);
                 dht_->connectivityChanged();
             }
-        } else {
-            JAMI_WARN("[Account %s] DHT port %u opened: restarting network", getAccountID().c_str(), newPort);
-            dht_->connectivityChanged();
-        }
-    }, dhtPort_, jami::upnp::PortType::UDP, false);
+        }, dhtPort_, jami::upnp::PortType::UDP, false);
+    } else
+        onLoad();
 }
 
 void
@@ -1512,10 +1537,10 @@ JamiAccount::doRegister()
     }
 
     /* if UPnP is enabled, then wait for IGD to complete registration */
-    if (upnp_) {
+    if (upnp_ or proxyServerCached_.empty()) {
         JAMI_DBG("UPnP: Attempting to map ports for Jami account");
         setRegistrationState(RegistrationState::TRYING);
-        mapPortUPnP();
+        registerAsyncOps();
     } else {
         doRegister_();
     }
@@ -1668,7 +1693,7 @@ JamiAccount::doRegister_()
         config.dht_config.node_config.maintain_storage = false;
         config.dht_config.node_config.persist_path = cachePath_+DIR_SEPARATOR_STR "dhtstate";
         config.dht_config.id = id_;
-        config.proxy_server = getDhtProxyServer();
+        config.proxy_server = getDhtProxyServer(proxyServer_);
         config.push_node_id = getAccountID();
         config.push_token = deviceKey_;
         config.threaded = true;
@@ -1830,7 +1855,7 @@ JamiAccount::doRegister_()
 
         dhtPeerConnector_->onDhtConnected(accountManager_->getInfo()->deviceId);
 
-        std::lock_guard<std::mutex> lock(buddyInfoMtx);
+        std::lock_guard<std::mutex> bLock(buddyInfoMtx);
         for (auto& buddy : trackedBuddies_) {
             buddy.second.devices_cnt = 0;
             trackPresence(buddy.first, buddy.second);
@@ -2127,6 +2152,7 @@ JamiAccount::getKnownDevices() const
 tls::DhParams
 JamiAccount::loadDhParams(std::string path)
 {
+    std::lock_guard<std::mutex> l(fileutils::getFileLock(path));
     try {
         // writeTime throw exception if file doesn't exist
         auto duration = clock::now() - fileutils::writeTime(path);
@@ -2151,14 +2177,90 @@ JamiAccount::loadDhParams(std::string path)
     }
 }
 
+void
+JamiAccount::loadCachedUrl(const std::string& url,
+                const std::string& cachePath,
+                const std::chrono::seconds& cacheDuration,
+                std::function<void(const dht::http::Response& response)> cb)
+{
+    auto lock = std::make_shared<std::lock_guard<std::mutex>>(fileutils::getFileLock(cachePath));
+    dht::ThreadPool::io().run([lock, cb, url, cachePath, cacheDuration, w=weak()]() {
+        try {
+            // writeTime throws exception if file doesn't exist
+            auto duration = clock::now() - fileutils::writeTime(cachePath);
+            if (duration > cacheDuration)
+                throw std::runtime_error("file too old");
+
+            JAMI_DBG("Loading '%.*s' from cache file '%.*s'", (int)url.size(), url.c_str(), (int)cachePath.size(), cachePath.c_str());
+            auto data = fileutils::loadFile(cachePath);
+            dht::http::Response ret;
+            ret.body = {data.begin(), data.end()};
+            ret.status_code = 200;
+            cb(ret);
+        } catch (const std::exception& e) {
+            JAMI_DBG("Failed to load '%.*s' from '%.*s': %s", (int)url.size(), url.c_str(), (int)cachePath.size(), cachePath.c_str(), e.what());
+
+            if (auto sthis = w.lock()) {
+                auto ioContext = Manager::instance().ioContext();
+                auto req = std::make_shared<dht::http::Request>(*ioContext, url, [lock, cb, cachePath, w](const dht::http::Response& response) {
+                    if (response.status_code == 200) {
+                        try {
+                            fileutils::saveFile(cachePath, (const uint8_t*)response.body.data(), response.body.size(), 0600);
+                            JAMI_DBG("Cached result to '%.*s'", (int)cachePath.size(), cachePath.c_str());
+                        } catch (const std::exception& ex) {
+                            JAMI_WARN("Failed to save result to %.*s: %s", (int)cachePath.size(), cachePath.c_str(), ex.what());
+                        }
+                    } else {
+                        JAMI_WARN("Failed to download url");
+                    }
+                    cb(response);
+                    if (auto req = response.request.lock())
+                        if (auto sthis = w.lock())
+                            sthis->requests_.erase(req);
+                });
+                sthis->requests_.emplace(req);
+                req->send();
+            }
+        }
+    });
+}
+
+void
+JamiAccount::loadCachedProxyServer(std::function<void(const std::string& proxy)> cb)
+{
+    if (proxyServerCached_.empty()) {
+        JAMI_DBG("[Account %s] loading DHT proxy URL", getAccountID().c_str());
+        if (proxyListUrl_.empty()) {
+            cb(getDhtProxyServer(proxyServer_));
+        } else {
+            loadCachedUrl(proxyListUrl_,
+                cachePath_ + DIR_SEPARATOR_STR "dhtproxylist",
+                std::chrono::hours(24 * 3),
+                [w=weak(), cb=std::move(cb)](const dht::http::Response& response){
+                    if (auto sthis = w.lock()) {
+                        sthis->proxyServerCached_.clear();
+                        if (response.status_code == 200) {
+                            cb(sthis->getDhtProxyServer(response.body));
+                        } else {
+                            cb(sthis->getDhtProxyServer(sthis->proxyServer_));
+                        }
+                    }
+                }
+            );
+        }
+    } else {
+        cb(proxyServerCached_);
+    }
+}
+
 std::string
-JamiAccount::getDhtProxyServer()
+JamiAccount::getDhtProxyServer(const std::string& serverList)
 {
     if (!proxyEnabled_) return {};
     if (proxyServerCached_.empty()) {
         std::vector<std::string> proxys;
         // Split the list of servers
-        std::sregex_iterator begin = {proxyServer_.begin(), proxyServer_.end(), PROXY_REGEX}, end;
+        std::sregex_iterator begin = {serverList.begin(), serverList.end(), PROXY_REGEX}, end;
         for (auto it = begin; it != end; ++it) {
             auto &match = *it;
             if (match[5].matched and match[6].matched) {
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index 0596524400399dd4dc6c8448f3dc17d596c06f4e..1f66e94e6d2e22a4abefe5b85f9858c658710a6c 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -59,9 +59,9 @@ class Emitter;
 
 namespace dev
 {
-    template <unsigned N> class FixedHash;
-    using h160 = FixedHash<20>;
-    using Address = h160;
+template <unsigned N> class FixedHash;
+using h160 = FixedHash<20>;
+using Address = h160;
 }
 
 namespace jami {
@@ -85,6 +85,8 @@ public:
     constexpr static const in_port_t DHT_DEFAULT_PORT = 4222;
     constexpr static const char* const DHT_DEFAULT_BOOTSTRAP = "bootstrap.jami.net";
     constexpr static const char* const DHT_DEFAULT_PROXY = "dhtproxy.jami.net:[80-100]";
+    constexpr static const char* const DHT_DEFAULT_BOOTSTRAP_LIST_URL = "https://config.jami.net/boostrapList";
+    constexpr static const char* const DHT_DEFAULT_PROXY_LIST_URL = "https://config.jami.net/proxyList";
 
     /* constexpr */ static const std::pair<uint16_t, uint16_t> DHT_PORT_RANGE;
 
@@ -477,9 +479,9 @@ private:
     void onTrackedBuddyOnline(const dht::InfoHash&);
 
     /**
-     * Maps require port via UPnP
+     * Maps require port via UPnP and other async ops
      */
-    void mapPortUPnP();
+    void registerAsyncOps();
     /**
      * Add port mapping callback function.
      */
@@ -525,7 +527,13 @@ private:
 
     static tls::DhParams loadDhParams(std::string path);
 
-    std::string getDhtProxyServer();
+    void loadCachedUrl(const std::string& url,
+                    const std::string& cachePath,
+                    const std::chrono::seconds& cacheDuration,
+                    std::function<void(const dht::http::Response& response)>);
+
+    std::string getDhtProxyServer(const std::string& serverList);
+    void loadCachedProxyServer(std::function<void(const std::string&)> cb);
 
     /**
      * The TLS settings, used only if tls is chosen as a sip transport.
@@ -541,7 +549,7 @@ private:
     std::string nameServer_;
     std::string registeredName_;
 #endif
-        std::shared_ptr<dht::Logger> logger_;
+    std::shared_ptr<dht::Logger> logger_;
 
     std::shared_ptr<dht::DhtRunner> dht_ {};
     std::unique_ptr<AccountManager> accountManager_;
@@ -583,6 +591,8 @@ private:
     mutable std::mutex dhtValuesMtx_;
     bool dhtPublicInCalls_ {true};
 
+    std::string bootstrapListUrl_;
+
     /**
      * DHT port preference
      */
@@ -601,6 +611,7 @@ private:
     /**
      * Proxy
      */
+    std::string proxyListUrl_;
     bool proxyEnabled_ {false};
     std::string proxyServer_ {};
     std::string proxyServerCached_ {};
@@ -656,6 +667,8 @@ private:
      * Jami, or for each connectivityChange()
      */
     void cacheTurnServers();
+
+    std::set<std::shared_ptr<dht::http::Request>> requests_;
 };
 
 static inline std::ostream& operator<< (std::ostream& os, const JamiAccount& acc)
diff --git a/src/jamidht/namedirectory.h b/src/jamidht/namedirectory.h
index c35b76f6835898d26eeaea9852e3696c829f1384..8b70b9132d4eef54c4c2bb1f377c7d5eb46941bc 100644
--- a/src/jamidht/namedirectory.h
+++ b/src/jamidht/namedirectory.h
@@ -36,6 +36,7 @@ struct PublicKey;
 }
 namespace http {
 class Request;
+struct Response;
 class Resolver;
 }
 struct Logger;
@@ -96,7 +97,7 @@ private:
      */
     std::shared_ptr<asio::io_context> httpContext_;
     std::shared_ptr<dht::http::Resolver> resolver_;
-    std::map<unsigned int /*id*/, std::shared_ptr<dht::http::Request>> requests_;
+    std::map<unsigned, std::shared_ptr<dht::http::Request>> requests_;
 
     std::map<std::string, std::string> nameCache_ {};
     std::map<std::string, std::string> addrCache_ {};