diff --git a/src/account.cpp b/src/account.cpp
index b9a70ae8dd464bde961cbde725e636159b5766f7..a391b395f58f37a7f2ce21bb3982066bed7ccac3 100644
--- a/src/account.cpp
+++ b/src/account.cpp
@@ -543,7 +543,7 @@ Account::getUPnPActive() const
 {
     std::lock_guard<std::mutex> lk(upnp_mtx);
     if (upnpCtrl_)
-        return upnpCtrl_->hasValidIGD();
+        return upnpCtrl_->isReady();
     return false;
 }
 
diff --git a/src/ice_transport.cpp b/src/ice_transport.cpp
index 146eaceae8bc11f8cb001d973f9a98e65573bcc0..cabb535ab0ec6ca687868581079a5db5abb80e89 100644
--- a/src/ice_transport.cpp
+++ b/src/ice_transport.cpp
@@ -552,9 +552,9 @@ IceTransport::Impl::handleEvents(unsigned max_msec)
 void
 IceTransport::Impl::onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op, pj_status_t status)
 {
-    const char* opname = op == PJ_ICE_STRANS_OP_INIT          ? "initialization"
-                         : op == PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation"
-                                                              : "unknown_op";
+    const char* opname = op == PJ_ICE_STRANS_OP_INIT
+                             ? "initialization"
+                             : op == PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation" : "unknown_op";
 
     const bool done = status == PJ_SUCCESS;
     if (done) {
@@ -793,8 +793,9 @@ IceTransport::Impl::requestUpnpMappings()
         // Request the mapping
         Mapping::sharedPtr_t mapPtr = upnp_->reserveMapping(requestedMap);
 
-        // To use a mapping, it must be valid and open.
-        if (mapPtr and mapPtr->getMapKey() and (mapPtr->getState() == MappingState::OPEN)) {
+        // To use a mapping, it must be valid, open and has valid host address.
+        if (mapPtr and mapPtr->getMapKey() and (mapPtr->getState() == MappingState::OPEN)
+            and mapPtr->hasValidHostAddress()) {
             std::lock_guard<std::mutex> lock(upnpMappingsMutex_);
             auto ret = upnpMappings_.emplace(mapPtr->getMapKey(), *mapPtr);
             if (ret.second) {
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index a111671c3f1ccc1a956ce59c065bd2c643f6e4d2..721838f0bc7a0b2c6cb6ee432fe54b0e742de105 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -3376,7 +3376,7 @@ JamiAccount::storeActiveIpAddress()
                  publicAddr.toString().c_str());
 
         // Check if we need to create a new UPNP controller instance.
-        if (upnpEnabled_ and (not upnpCtrl_ or not upnpCtrl_->hasValidIGD())) {
+        if (upnpEnabled_ and (not upnpCtrl_ or not upnpCtrl_->isReady())) {
             enableUpnp(true);
         }
     }
diff --git a/src/upnp/protocol/mapping.cpp b/src/upnp/protocol/mapping.cpp
index 51fa3522a1a5b29475c0e64f38774e1154cadad9..0ede1470729c7df0354ff0e0d1a7307822fd51db 100644
--- a/src/upnp/protocol/mapping.cpp
+++ b/src/upnp/protocol/mapping.cpp
@@ -26,8 +26,9 @@ namespace jami {
 namespace upnp {
 
 Mapping::Mapping(uint16_t portExternal, uint16_t portInternal, PortType type, bool available)
-    : externalAddr_()
-    , internalAddr_()
+    : internalAddr_()
+    , internalPort_(portInternal)
+    , externalPort_(portExternal)
     , type_(type)
     , igd_()
     , available_(available)
@@ -38,32 +39,15 @@ Mapping::Mapping(uint16_t portExternal, uint16_t portInternal, PortType type, bo
 #if HAVE_LIBNATPMP
     , renewalTime_(sys_clock::now())
 #endif
-{
-    externalAddr_.port_ = portExternal;
-    internalAddr_.port_ = portInternal;
-}
-
-Mapping::Mapping(Mapping&& other) noexcept
-    : externalAddr_(std::move(other.externalAddr_))
-    , internalAddr_(std::move(other.internalAddr_))
-    , type_(other.type_)
-    , igd_(other.igd_)
-    , available_(other.available_)
-    , state_(other.state_)
-    , notifyCb_(std::move(other.notifyCb_))
-    , timeoutTimer_(std::move(other.timeoutTimer_))
-    , autoUpdate_(other.autoUpdate_)
-#if HAVE_LIBNATPMP
-    , renewalTime_(other.renewalTime_)
-#endif
 {}
 
 Mapping::Mapping(const Mapping& other)
 {
     std::lock_guard<std::mutex> lock(mutex_);
 
-    externalAddr_ = other.externalAddr_;
     internalAddr_ = other.internalAddr_;
+    internalPort_ = other.internalPort_;
+    externalPort_ = other.externalPort_;
     type_ = other.type_;
     igd_ = other.igd_;
     available_ = other.available_;
@@ -82,8 +66,9 @@ Mapping::operator=(Mapping&& other) noexcept
     std::lock_guard<std::mutex> lock(mutex_);
 
     if (this != &other) {
-        externalAddr_ = other.externalAddr_;
         internalAddr_ = other.internalAddr_;
+        internalPort_ = other.internalPort_;
+        externalPort_ = other.externalPort_;
         type_ = other.type_;
         igd_ = other.igd_;
         other.igd_.reset();
@@ -109,16 +94,16 @@ Mapping::operator==(const Mapping& other) const noexcept
 {
     std::lock_guard<std::mutex> lock(mutex_);
 
-    if (externalAddr_.addr_ != other.externalAddr_.addr_)
-        return false;
-    if (externalAddr_.port_ != other.externalAddr_.port_)
+    if (internalAddr_ != other.internalAddr_)
         return false;
-    if (internalAddr_.addr_ != other.internalAddr_.addr_)
+    if (internalPort_ != other.internalPort_)
         return false;
-    if (internalAddr_.port_ != other.internalAddr_.port_)
+    if (externalPort_ != other.externalPort_)
         return false;
     if (type_ != other.type_)
         return false;
+    if (igd_ != other.igd_)
+        return false;
     return true;
 }
 
@@ -128,6 +113,20 @@ Mapping::operator!=(const Mapping& other) const noexcept
     return not(*this == other);
 }
 
+void
+Mapping::updateFrom(const Mapping& other)
+{
+    if (internalPort_ != other.internalPort_ or type_ != other.type_) {
+        JAMI_ERR("The source and destination mappings must match (same type and port)");
+        assert(false);
+    }
+
+    internalAddr_ = std::move(other.internalAddr_);
+    externalPort_ = other.externalPort_;
+    igd_ = other.igd_;
+    state_ = other.state_;
+}
+
 void
 Mapping::setAvailable(bool val)
 {
@@ -171,7 +170,7 @@ Mapping::toString(bool addState) const
     std::lock_guard<std::mutex> lock(mutex_);
     std::ostringstream descr;
     descr << UPNP_MAPPING_DESCRIPTION_PREFIX << "-" << getTypeStr(type_);
-    descr << ":" << std::to_string(externalAddr_.port_);
+    descr << ":" << std::to_string(internalPort_);
 
     if (addState)
         descr << " (state=" << getStateStr(state_) << ")";
@@ -182,16 +181,41 @@ Mapping::toString(bool addState) const
 bool
 Mapping::isValid() const
 {
-    std::lock_guard<std::mutex> lock(mutex_);
-    if (externalAddr_.port_ == 0)
-        return false;
-    if (internalAddr_.port_ == 0)
-        return false;
-    if (state_ == MappingState::FAILED)
+    {
+        std::lock_guard<std::mutex> lock(mutex_);
+        if (state_ == MappingState::FAILED)
+            return false;
+        if (internalPort_ == 0)
+            return false;
+        if (externalPort_ == 0)
+            return false;
+        if (not igd_ or not igd_->isValid())
+            return false;
+    }
+
+    if (not hasValidHostAddress())
         return false;
+
     return true;
 }
 
+bool
+Mapping::hasValidHostAddress() const
+{
+    std::lock_guard<std::mutex> lock(mutex_);
+
+    IpAddr intAddr(internalAddr_);
+    return intAddr and not intAddr.isLoopback();
+}
+
+bool
+Mapping::hasPublicAddress() const
+{
+    std::lock_guard<std::mutex> lock(mutex_);
+
+    return igd_ and igd_->getPublicIp() and not igd_->getPublicIp().isPrivate();
+}
+
 void
 Mapping::setTimeoutTimer(std::shared_ptr<Task> timer)
 {
@@ -218,7 +242,7 @@ Mapping::getMapKey() const
 {
     std::lock_guard<std::mutex> lock(mutex_);
 
-    key_t mapKey = externalAddr_.port_;
+    key_t mapKey = internalPort_;
     if (type_ == PortType::UDP)
         mapKey |= 1 << (sizeof(uint16_t) * 8);
     return mapKey;
@@ -230,74 +254,69 @@ Mapping::getTypeFromMapKey(key_t key)
     return (key >> (sizeof(uint16_t) * 8)) ? PortType::UDP : PortType::TCP;
 }
 
-void
-Mapping::setExternalAddress(const std::string& addr)
-{
-    std::lock_guard<std::mutex> lock(mutex_);
-    externalAddr_.addr_ = addr;
-}
-
 std::string
 Mapping::getExternalAddress() const
 {
     std::lock_guard<std::mutex> lock(mutex_);
-    return externalAddr_.addr_;
+    if (igd_)
+        return igd_->getPublicIp().toString();
+    return {};
 }
 
 void
 Mapping::setExternalPort(uint16_t port)
 {
     std::lock_guard<std::mutex> lock(mutex_);
-    externalAddr_.port_ = port;
+    externalPort_ = port;
 }
 
 uint16_t
 Mapping::getExternalPort() const
 {
     std::lock_guard<std::mutex> lock(mutex_);
-    return externalAddr_.port_;
+    return externalPort_;
 }
 
 std::string
 Mapping::getExternalPortStr() const
 {
     std::lock_guard<std::mutex> lock(mutex_);
-    return std::to_string(externalAddr_.port_);
+    return std::to_string(externalPort_);
 }
 
 void
 Mapping::setInternalAddress(const std::string& addr)
 {
     std::lock_guard<std::mutex> lock(mutex_);
-    internalAddr_.addr_ = addr;
+    internalAddr_ = addr;
 }
 
 std::string
 Mapping::getInternalAddress() const
 {
     std::lock_guard<std::mutex> lock(mutex_);
-    return internalAddr_.addr_;
+    return internalAddr_;
 }
 
 void
 Mapping::setInternalPort(uint16_t port)
 {
     std::lock_guard<std::mutex> lock(mutex_);
-    internalAddr_.port_ = port;
+    internalPort_ = port;
 }
 
 uint16_t
 Mapping::getInternalPort() const
 {
     std::lock_guard<std::mutex> lock(mutex_);
-    return internalAddr_.port_;
+    return internalPort_;
 }
 
 std::string
 Mapping::getInternalPortStr() const
 {
     std::lock_guard<std::mutex> lock(mutex_);
-    return std::to_string(internalAddr_.port_);
+    return std::to_string(internalPort_);
 }
 
 PortType
diff --git a/src/upnp/protocol/mapping.h b/src/upnp/protocol/mapping.h
index 25bbfe3f49044409fe1f70bfa74e5cec53b8a22c..694784e5b2221c22a19192e0e66314c0c4861334 100644
--- a/src/upnp/protocol/mapping.h
+++ b/src/upnp/protocol/mapping.h
@@ -58,31 +58,6 @@ public:
     using sharedPtr_t = std::shared_ptr<Mapping>;
     using NotifyCallback = std::function<void(sharedPtr_t)>;
 
-    struct MappingAddr
-    {
-        std::string addr_ {};
-        uint16_t port_ {0};
-        MappingAddr()
-            : addr_()
-            , port_(0)
-        {}
-
-        MappingAddr(const MappingAddr& other)
-        {
-            addr_ = other.addr_;
-            port_ = other.port_;
-        }
-
-        MappingAddr& operator=(const MappingAddr& other)
-        {
-            if (this != &other) {
-                addr_ = other.addr_;
-                port_ = other.port_;
-            }
-            return *this;
-        }
-    };
-
     static constexpr char const* MAPPING_STATE_STR[4] {"PENDING", "IN_PROGRESS", "FAILED", "OPEN"};
     static constexpr char const* UPNP_MAPPING_DESCRIPTION_PREFIX {"JAMI"};
 
@@ -90,8 +65,8 @@ public:
             uint16_t portInternal = 0,
             PortType type = PortType::UDP,
             bool available = true);
-    Mapping(Mapping&& other) noexcept;
     Mapping(const Mapping& other);
+    Mapping(Mapping&& other) = delete;
     ~Mapping() = default;
 
     Mapping& operator=(Mapping&& other) noexcept;
@@ -106,6 +81,7 @@ public:
 
     inline explicit operator bool() const { return isValid(); }
 
+    void updateFrom(const Mapping& other);
     std::string getExternalAddress() const;
     uint16_t getExternalPort() const;
     std::string getExternalPortStr() const;
@@ -127,6 +103,8 @@ public:
     }
     std::string toString(bool addState = false) const;
     bool isValid() const;
+    bool hasValidHostAddress() const;
+    bool hasPublicAddress() const;
     void setNotifyCallback(NotifyCallback cb);
     void enableAutoUpdate(bool enable);
     bool getAutoUpdate() const;
@@ -138,10 +116,8 @@ public:
 
 private:
     NotifyCallback getNotifyCallback() const;
-    // TODO_MC. Remove SetXXadress. already have IGD. Set ports instead.
-    void setExternalAddress(const std::string& addr);
-    void setExternalPort(uint16_t port);
     void setInternalAddress(const std::string& addr);
+    void setExternalPort(uint16_t port);
     void setInternalPort(uint16_t port);
 
     void setIgd(const std::shared_ptr<IGD>& igd);
@@ -155,8 +131,9 @@ private:
 #endif
 
     mutable std::mutex mutex_;
-    MappingAddr externalAddr_;
-    MappingAddr internalAddr_;
+    std::string internalAddr_;
+    uint16_t internalPort_ {0};
+    uint16_t externalPort_ {0};
     PortType type_;
     // Protocol and
     std::shared_ptr<IGD> igd_;
diff --git a/src/upnp/protocol/natpmp/nat_pmp.cpp b/src/upnp/protocol/natpmp/nat_pmp.cpp
index 37ea9fa4ab8f303089ea03a206c54bddd6285afa..6a58e7af12ff69ee6452875f0bdf4372dfac09a2 100644
--- a/src/upnp/protocol/natpmp/nat_pmp.cpp
+++ b/src/upnp/protocol/natpmp/nat_pmp.cpp
@@ -20,6 +20,8 @@
 
 #include "nat_pmp.h"
 
+#if HAVE_LIBNATPMP
+
 namespace jami {
 namespace upnp {
 
@@ -46,10 +48,16 @@ NatPmp::initNatPmp()
 
     CHECK_VALID_THREAD();
 
-    userLocalIp_ = ip_utils::getLocalAddr(pj_AF_INET());
     initialized_ = false;
-    assert(igd_);
+    hostAddress_ = ip_utils::getLocalAddr(pj_AF_INET());
+
+    // Local address must be valid.
+    if (not getHostAddress() or getHostAddress().isLoopback()) {
+        JAMI_WARN("NAT-PMP: Does not have a valid local address!");
+        return;
+    }
 
+    assert(igd_);
     if (igd_->isValid()) {
         igd_->setValid(false);
         processIgdUpdate(UpnpIgdEvent::REMOVED);
@@ -91,6 +99,9 @@ NatPmp::initNatPmp()
 
     // Set the local (gateway) address.
     igd_->setLocalIp(igdAddr);
+    // NAT-PMP protocol does not have UID, but we will set generic
+    // one debugging purposes.
+    igd_->setUID("NAT-PMP Gateway");
 
     // Search and set the public address.
     getIgdPublicAddress();
@@ -143,14 +154,6 @@ NatPmp::searchForIgd()
         initNatPmp();
     }
 
-    if (initialized_ and not hasValidIgd()) {
-        // Log current IGD.
-        JAMI_DBG("NAT-PMP: Current top IGD [%s:%s] valid %s",
-                 igd_->getLocalIp().toString().c_str(),
-                 igd_->getLocalIp().toString().c_str(),
-                 igd_->isValid() ? "YES" : "NO");
-    }
-
     // Schedule a retry in case init failed.
     if (not initialized_) {
         if (igdSearchCounter_++ < MAX_RESTART_SEARCH_RETRIES) {
@@ -175,8 +178,11 @@ NatPmp::getIgdList(std::list<std::shared_ptr<IGD>>& igdList) const
 }
 
 bool
-NatPmp::hasValidIgd() const
+NatPmp::isReady() const
 {
+    // Must at least have a valid local address.
+    if (not getHostAddress() or getHostAddress().isLoopback())
+        return false;
     return igd_ and igd_->isValid();
 }
 
@@ -332,8 +338,7 @@ NatPmp::addPortMapping(const std::shared_ptr<IGD>& igdIn, Mapping& mapping, bool
     Mapping mapToAdd(mapping.getExternalPort(),
                      mapping.getInternalPort(),
                      mapping.getType() == PortType::UDP ? upnp::PortType::UDP : upnp::PortType::TCP);
-    mapToAdd.setExternalAddress(igd_->getPublicIp().toString());
-    mapToAdd.setInternalAddress(getUserLocalIp().toString());
+    mapToAdd.setInternalAddress(getHostAddress().toString());
     mapToAdd.setIgd(igd_);
 
     uint32_t lifetime = MAPPING_ALLOCATION_LIFETIME;
@@ -639,3 +644,5 @@ NatPmp::processMappingRemoved(const Mapping& map)
 
 } // namespace upnp
 } // namespace jami
+
+#endif //-- #if HAVE_LIBNATPMP
diff --git a/src/upnp/protocol/natpmp/nat_pmp.h b/src/upnp/protocol/natpmp/nat_pmp.h
index 2df50e4a6991dadf09fee7d74d9881a0373e4ec4..ddf0a7ea8bfa40916b321a826cc7ca565625d150 100644
--- a/src/upnp/protocol/natpmp/nat_pmp.h
+++ b/src/upnp/protocol/natpmp/nat_pmp.h
@@ -83,7 +83,7 @@ public:
     void getIgdList(std::list<std::shared_ptr<IGD>>& igdList) const override;
 
     // Return true if it has at least one valid IGD.
-    bool hasValidIgd() const override;
+    bool isReady() const override;
 
     // Increment errors counter.
     void incrementErrorsCounter(const std::shared_ptr<IGD>& igd) override;
diff --git a/src/upnp/protocol/pupnp/pupnp.cpp b/src/upnp/protocol/pupnp/pupnp.cpp
index 2bedf3ad26ead03b47c437cd5a3be372fc32045b..cf6334101cca85ec35c5515381d4c3d3e044fa0c 100644
--- a/src/upnp/protocol/pupnp/pupnp.cpp
+++ b/src/upnp/protocol/pupnp/pupnp.cpp
@@ -94,11 +94,12 @@ errorOnResponse(IXML_Document* doc)
     return false;
 }
 
-
 // UPNP class implementation
 
 PUPnP::PUPnP()
 {
+    threadId_ = getCurrentThread();
+
     if (not initUpnpLib())
         return;
     if (not registerClient())
@@ -249,10 +250,12 @@ PUPnP::setObserver(UpnpMappingObserver* obs)
 void
 PUPnP::searchForDevices()
 {
-    userLocalIp_ = ip_utils::getLocalAddr(pj_AF_INET());
+    std::unique_lock<std::mutex> lk(ctrlptMutex_);
+
+    JAMI_DBG("PUPnP: Send IGD search request");
+
     // Send out search for multiple types of devices, as some routers may possibly
     // only reply to one.
-    std::unique_lock<std::mutex> lk(ctrlptMutex_);
     UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_ROOT_DEVICE, this);
     UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_IGD_DEVICE, this);
     UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANIP_SERVICE, this);
@@ -264,6 +267,15 @@ PUPnP::clearIgds()
 {
     JAMI_DBG("PUPnP: clearing IGDs and devices lists");
 
+    CHECK_VALID_THREAD();
+
+    hostAddress_ = {};
+
+    if (searchForIgdTimer_)
+        searchForIgdTimer_->cancel();
+
+    igdSearchCounter_ = 0;
+
     {
         std::lock_guard<std::mutex> lock(validIgdListMutex_);
         validIgdList_.clear();
@@ -281,9 +293,30 @@ PUPnP::clearIgds()
 void
 PUPnP::searchForIgd()
 {
-    // Notify thread to execute in non-blocking fashion.
-    searchForIgd_ = true;
-    pupnpCv_.notify_one();
+    CHECK_VALID_THREAD();
+
+    // Update local address before searching.
+    hostAddress_ = ip_utils::getLocalAddr(pj_AF_INET());
+
+    if (not isReady()) {
+        if (igdSearchCounter_++ < PUPNP_MAX_RESTART_SEARCH_RETRIES) {
+            JAMI_DBG("PUPnP: Start search for IGD: attempt %u", igdSearchCounter_);
+
+            searchForIgd_ = true;
+            pupnpCv_.notify_one();
+
+            // Cancel the current timer (if any) and re-schedule.
+            if (searchForIgdTimer_)
+                searchForIgdTimer_->cancel();
+
+            searchForIgdTimer_ = getUpnContextScheduler()
+                                     ->scheduleIn([this] { searchForIgd(); },
+                                                  PUPNP_TIMEOUT_BEFORE_IGD_SEARCH_RETRY);
+        } else {
+            JAMI_WARN("PUPnP: Setup failed after %u trials. PUPnP will be disabled!",
+                      PUPNP_MAX_RESTART_SEARCH_RETRIES);
+        }
+    }
 }
 
 void
@@ -298,6 +331,16 @@ PUPnP::getIgdList(std::list<std::shared_ptr<IGD>>& igdList) const
     }
 }
 
+bool
+PUPnP::isReady() const
+{
+    // Must at least have a valid local address.
+    if (not getHostAddress() or getHostAddress().isLoopback())
+        return false;
+
+    return hasValidIgd();
+}
+
 bool
 PUPnP::hasValidIgd() const
 {
@@ -309,6 +352,17 @@ PUPnP::hasValidIgd() const
     return false;
 }
 
+bool
+PUPnP::updateAndCheckHostAddress()
+{
+    if (hostAddress_ and not hostAddress_.isLoopback())
+        return true;
+
+    hostAddress_ = ip_utils::getLocalAddr(pj_AF_INET());
+
+    return hostAddress_ and not hostAddress_.isLoopback();
+}
+
 void
 PUPnP::incrementErrorsCounter(const std::shared_ptr<IGD>& igd)
 {
@@ -344,8 +398,7 @@ PUPnP::validateIgd(const IGDInfo& info)
         return false;
     }
 
-    JAMI_DBG("PUPnP: Validating IGD candidate\n"
-             "    UDN          : %s\n"
+    JAMI_DBG("PUPnP: Validating the IGD candidate [UDN: %s]\n"
              "    Name         : %s\n"
              "    Service Type : %s\n"
              "    Service ID   : %s\n"
@@ -404,7 +457,8 @@ PUPnP::validateIgd(const IGDInfo& info)
             // Must not be a null pointer
             assert(igd.get() != nullptr);
             if (*igd == *igd_candidate) {
-                JAMI_DBG("PUPnP: Device [%s] with int/ext addresses [%s:%s] is already in the list",
+                JAMI_DBG("PUPnP: Device [%s] with int/ext addresses [%s:%s] is already in the list "
+                         "of valid IGDs",
                          igd_candidate->getUID().c_str(),
                          igd_candidate->getLocalIp().toString().c_str(),
                          igd_candidate->getPublicIp().toString().c_str());
@@ -413,8 +467,10 @@ PUPnP::validateIgd(const IGDInfo& info)
         }
     }
 
-    JAMI_DBG("PUPnP: Found new valid IGD [%s] with int/ext addresses [%s:%s]",
-             igd_candidate->getUID().c_str(),
+    JAMI_DBG("PUPnP: Added a new IGD [%s] to the list of valid IGDs",
+             igd_candidate->getUID().c_str());
+
+    JAMI_DBG("PUPnP: New IGD addresses [int: %s - ext: %s]",
              igd_candidate->getLocalIp().toString().c_str(),
              igd_candidate->getPublicIp().toString().c_str());
 
@@ -448,7 +504,6 @@ void
 PUPnP::requestMappingAdd(const std::shared_ptr<IGD>& igd, const Mapping& mapping)
 {
     if (auto pupnp_igd = std::dynamic_pointer_cast<const UPnPIGD>(igd)) {
-        JAMI_DBG("PUPnP: Attempting to open port %s", mapping.toString().c_str());
         actionAddPortMappingAsync(*pupnp_igd, mapping);
     }
 }
@@ -486,7 +541,7 @@ PUPnP::actionAddPortMappingAsync(const UPnPIGD& igd, const Mapping& mapping)
                     ACTION_ADD_PORT_MAPPING,
                     igd.getServiceType().c_str(),
                     "NewInternalClient",
-                    getUserLocalIp().toString().c_str());
+                    getHostAddress().toString().c_str());
     UpnpAddToAction(&action_container_ptr,
                     ACTION_ADD_PORT_MAPPING,
                     igd.getServiceType().c_str(),
@@ -537,8 +592,7 @@ PUPnP::processAddMapAction(const std::string& ctrlURL,
         for (auto const& it : validIgdList_) {
             if (auto igd = std::dynamic_pointer_cast<UPnPIGD>(it)) {
                 if (igd->getControlURL() == ctrlURL) {
-                    mapToAdd.setExternalAddress(igd->getPublicIp().toString());
-                    mapToAdd.setInternalAddress(getUserLocalIp().toString());
+                    mapToAdd.setInternalAddress(getHostAddress().toString());
                     mapToAdd.setIgd(igd);
                     break;
                 }
@@ -590,9 +644,6 @@ PUPnP::requestMappingRemove(const Mapping& igdMapping)
     // Iterate over all IGDs in internal list and try to remove selected mapping.
     for (auto const& it : validIgdList_) {
         if (auto igd = std::dynamic_pointer_cast<UPnPIGD>(it)) {
-            JAMI_DBG("PUPnP: Attempting to close port %s %s",
-                     igdMapping.getExternalPortStr().c_str(),
-                     igdMapping.getTypeStr());
             std::lock_guard<std::mutex> lk2(ctrlptMutex_);
             actionDeletePortMappingAsync(*igd,
                                          igdMapping.getExternalPortStr(),
@@ -676,16 +727,25 @@ PUPnP::processDiscoverySearchResult(const std::string& cpDeviceId,
                                     const std::string& igdLocationUrl,
                                     const IpAddr& dstAddr)
 {
+    // Must first have a valid local address.
+    if (not updateAndCheckHostAddress()) {
+        JAMI_WARN("PUPnP: Local address is invalid. Ignore search result for now!");
+        return;
+    }
+
     dht::http::Url url(igdLocationUrl);
 
     std::lock_guard<std::mutex> lk(igdListMutex_);
 
-    if (not discoveredIgdList_.emplace(cpDeviceId).second)
+    // Use the device ID and the URL as ID. This is necessary as some
+    // IGDs may have the same device ID but different URLs.
+
+    auto igdId = cpDeviceId + " url: " + igdLocationUrl;
+
+    if (not discoveredIgdList_.emplace(igdId).second)
         return;
 
-    JAMI_DBG("PUPnP: Added IGD %s [%s] to known IGD list",
-             cpDeviceId.c_str(),
-             IpAddr(url.host).toString(true, true).c_str());
+    JAMI_DBG("PUPnP: Discovered a new IGD [%s]", igdId.c_str());
 
     // NOTE: here, we check if the location given is related to the source address.
     // If it's not the case, it's certainly a router plugged in the network, but not
@@ -698,8 +758,6 @@ PUPnP::processDiscoverySearchResult(const std::string& cpDeviceId,
                  IpAddr(url.host).toString(true, true).c_str(),
                  dstAddr.toString(true, true).c_str());
         return;
-    } else {
-        JAMI_DBG("PUPnP: Returned IGD location %s", IpAddr(url.host).toString(true, true).c_str());
     }
 
     dwnldlXmlList_.emplace_back(
@@ -834,11 +892,14 @@ PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event)
         break;
     }
     case UPNP_DISCOVERY_SEARCH_TIMEOUT: {
-        // Nothing to do here.
+        // Even if the discovery search is successful, it's normal to receive
+        // time-out events. This because we send search requests using various
+        // device types, which some of them may not return a response.
+        JAMI_DBG("PUPnP: Received discovery search time-out");
         break;
     }
     case UPNP_EVENT_RECEIVED: {
-        // TODO: Handle event by updating any changed state variables */
+        // Nothing to do.
         break;
     }
     case UPNP_EVENT_AUTORENEWAL_FAILED:   // Fall through. Treat failed autorenewal like an expired
@@ -933,18 +994,24 @@ PUPnP::subEventCallback(Upnp_EventType event_type, const void* event, void* user
 }
 
 int
-PUPnP::handleSubscriptionUPnPEvent(Upnp_EventType /*event_type */, const void* event)
+PUPnP::handleSubscriptionUPnPEvent(Upnp_EventType /*unused*/, const void* event)
 {
+    if (event == nullptr) {
+        JAMI_ERR("PUPnP: Unexpected null pointer!");
+        return UPNP_E_INVALID_ARGUMENT;
+    }
+
     const UpnpEventSubscribe* es_event = (const UpnpEventSubscribe*) event;
+    std::string publisherUrl(UpnpEventSubscribe_get_PublisherUrl_cstr(es_event));
+
     int upnp_err = UpnpEventSubscribe_get_ErrCode(es_event);
     if (upnp_err != UPNP_E_SUCCESS) {
-        JAMI_WARN("PUPnP: Error when trying to handle subscription callback -> %s",
-                  UpnpGetErrorMessage(upnp_err));
+        JAMI_WARN("PUPnP: Subscription error %s from %s",
+                  UpnpGetErrorMessage(upnp_err),
+                  publisherUrl.c_str());
         return upnp_err;
     }
 
-    // TODO: Handle subscription event.
-
     return UPNP_E_SUCCESS;
 }
 
@@ -1238,7 +1305,7 @@ PUPnP::getMappingsListByDescr(const std::shared_ptr<IGD>& igd, const std::string
         std::string desc_actual = getFirstDocItem(response.get(), "NewPortMappingDescription");
         std::string client_ip = getFirstDocItem(response.get(), "NewInternalClient");
 
-        if (client_ip != getUserLocalIp().toString()) {
+        if (client_ip != getHostAddress().toString()) {
             // Silently ignore un-matching addresses.
             continue;
         }
@@ -1284,7 +1351,7 @@ PUPnP::deleteMappingsByDescription(const std::shared_ptr<IGD>& igd, const std::s
              igd->getLocalIp().toString().c_str(),
              Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
 
-    auto mapList = getMappingsListByDescr(igd, Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
+    auto mapList = getMappingsListByDescr(igd, description);
 
     for (auto const& [_, map] : mapList) {
         requestMappingRemove(map);
@@ -1305,7 +1372,6 @@ PUPnP::actionDeletePortMapping(const UPnPIGD& igd,
     std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
         response(nullptr, ixmlDocument_free); // Response pointer.
     IXML_Document* action_container_ptr = nullptr;
-    IXML_Document* response_container_ptr = nullptr;
 
     // Set action name.
     std::string action_name {"DeletePortMapping"};
@@ -1345,7 +1411,7 @@ PUPnP::actionDeletePortMapping(const UPnPIGD& igd,
         return false;
     }
 
-    JAMI_DBG("PUPnP: Successfully sent %s request from %s",
+    JAMI_DBG("PUPnP: Successfully sent %s request to %s",
              action_name.c_str(),
              igd.getServiceType().c_str());
 
@@ -1396,7 +1462,7 @@ PUPnP::actionAddPortMapping(const UPnPIGD& igd,
                     action_name.c_str(),
                     igd.getServiceType().c_str(),
                     "NewInternalClient",
-                    getUserLocalIp().toString().c_str());
+                    getHostAddress().toString().c_str());
     UpnpAddToAction(&action_container_ptr,
                     action_name.c_str(),
                     igd.getServiceType().c_str(),
diff --git a/src/upnp/protocol/pupnp/pupnp.h b/src/upnp/protocol/pupnp/pupnp.h
index b431b01c03a3c89468682ee81582d01673f736d1..3290bab6ae65e737c4fa1bdff2052bdce4a3d750 100644
--- a/src/upnp/protocol/pupnp/pupnp.h
+++ b/src/upnp/protocol/pupnp/pupnp.h
@@ -70,6 +70,10 @@ constexpr static int CONFLICT_IN_MAPPING = 718;
 // Timeout values (in seconds).
 constexpr static unsigned int SEARCH_TIMEOUT {60};
 constexpr static unsigned int SUBSCRIBE_TIMEOUT {300};
+// Max number of IGD search attempts before failure.
+constexpr static unsigned int PUPNP_MAX_RESTART_SEARCH_RETRIES {5};
+// Time-out between two successive IGD search.
+constexpr static auto PUPNP_TIMEOUT_BEFORE_IGD_SEARCH_RETRY {std::chrono::seconds(60)};
 
 class PUPnP : public UPnPProtocol
 {
@@ -118,8 +122,8 @@ public:
     // Get the IGD list.
     void getIgdList(std::list<std::shared_ptr<IGD>>& igdList) const override;
 
-    // Return true if it has at least one valid IGD.
-    bool hasValidIgd() const override;
+    // Return true if the it's fully setup.
+    bool isReady() const override;
 
     // Increment IGD errors counter.
     void incrementErrorsCounter(const std::shared_ptr<IGD>& igd) override;
@@ -139,6 +143,14 @@ public:
     void requestMappingRemove(const Mapping& igdMapping) override;
 
 private:
+    ScheduledExecutor* getUpnContextScheduler() { return UpnpThreadUtil::getScheduler(); }
+
+    // Return true if it has at least one valid IGD.
+    bool hasValidIgd() const;
+
+    // Update and check the host (local) address. Returns true
+    // if the address is valid.
+    bool updateAndCheckHostAddress();
 
     // Delete mappings matching the description
     void deleteMappingsByDescription(const std::shared_ptr<IGD>& igd,
@@ -190,7 +202,7 @@ private:
 #endif
 
     // Callback subscription event function for handling subscription request.
-    int handleSubscriptionUPnPEvent(Upnp_EventType event_type, const void* event);
+    int handleSubscriptionUPnPEvent(Upnp_EventType /*unused*/, const void* event);
 
     // Parses the IGD candidate.
     std::unique_ptr<UPnPIGD> parseIgd(IXML_Document* doc, std::string locationUrl);
@@ -225,6 +237,8 @@ private:
     std::condition_variable pupnpCv_ {}; // Condition variable for thread-safe signaling.
     std::atomic_bool pupnpRun_ {true};   // Variable to allow the thread to run.
     std::thread pupnpThread_ {};         // PUPnP thread for non-blocking client registration.
+    std::shared_ptr<Task> searchForIgdTimer_ {};
+    unsigned int igdSearchCounter_ {0};
 
     mutable std::mutex validIgdListMutex_;
     std::list<std::shared_ptr<IGD>> validIgdList_; // List of valid IGDs.
diff --git a/src/upnp/protocol/upnp_protocol.h b/src/upnp/protocol/upnp_protocol.h
index 0b3e6bd181ec59107d0fc85abfbdb7a2c7af2f72..eed6cf00f6ed936e18160efa5291896acc44f90c 100644
--- a/src/upnp/protocol/upnp_protocol.h
+++ b/src/upnp/protocol/upnp_protocol.h
@@ -97,7 +97,7 @@ public:
     virtual void getIgdList(std::list<std::shared_ptr<IGD>>& igdList) const = 0;
 
     // Return true if it has at least one valid IGD.
-    virtual bool hasValidIgd() const = 0;
+    virtual bool isReady() const = 0;
 
     // Increment IGD errors counter.
     virtual void incrementErrorsCounter(const std::shared_ptr<IGD>& igd) = 0;
@@ -121,10 +121,12 @@ public:
     // Set the user callbacks.
     virtual void setObserver(UpnpMappingObserver* obs) = 0;
 
-    const IpAddr& getUserLocalIp() const { return userLocalIp_; }
+    // Get the host (local) address.
+    const IpAddr& getHostAddress() const { return hostAddress_; }
 
 protected:
-    IpAddr userLocalIp_ {};
+    // The host (local) address. Must be fully set before making any request.
+    IpAddr hostAddress_ {};
     UpnpMappingObserver* observer_;
 };
 
diff --git a/src/upnp/upnp_context.cpp b/src/upnp/upnp_context.cpp
index 09ade02c30377b293c78457d613b5e207e4811c8..772ddc6bfb484ed98104d7711777a9ae7d3ebf58 100644
--- a/src/upnp/upnp_context.cpp
+++ b/src/upnp/upnp_context.cpp
@@ -181,7 +181,7 @@ UPnPContext::setPublicAddress(const IpAddr& addr)
 }
 
 bool
-UPnPContext::hasValidIGD() const
+UPnPContext::isReady() const
 {
     std::lock_guard<std::mutex> lock(mappingMutex_);
     return not validIgdList_.empty();
@@ -365,9 +365,11 @@ UPnPContext::requestMapping(const std::shared_ptr<IGD>& igd, const Mapping::shar
 
     CHECK_VALID_THREAD();
 
-    JAMI_DBG("Request mapping %s using protocol [%s]",
+    JAMI_DBG("Request mapping %s using protocol [%s] IGD [%s %s]",
              map->toString().c_str(),
-             igd->getProtocolName());
+             igd->getProtocolName(),
+             igd->getUID().c_str(),
+             igd->getLocalIp().toString().c_str());
 
     // Register mapping timeout callback and update the state if needed.
     registerAddMappingTimeout(igd, map);
@@ -389,7 +391,7 @@ UPnPContext::requestMappingOnValidIgds(const Mapping::sharedPtr_t& map)
 
     CHECK_VALID_THREAD();
 
-    if (not hasValidIGD()) {
+    if (not isReady()) {
         JAMI_WARN("Can not request mapping, no valid IGD available!");
         return;
     }
@@ -399,12 +401,12 @@ UPnPContext::requestMappingOnValidIgds(const Mapping::sharedPtr_t& map)
 
 #if HAVE_LIBNATPMP
     // Prefere NAT-PMP if available.
-    if (protocolList_.at(NatProtocolType::NAT_PMP)->hasValidIgd())
+    if (protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
         proto = protocolList_.at(NatProtocolType::NAT_PMP);
 #endif
 
     // Request the mappings
-    if (proto->hasValidIgd()) {
+    if (proto->isReady()) {
         std::list<std::shared_ptr<IGD>> igdList;
         proto->getIgdList(igdList);
         for (auto const& igd : igdList)
@@ -496,19 +498,21 @@ UPnPContext::updateMappingList(bool async)
         mappingListUpdateTimer_->cancel();
     mappingListUpdateTimer_ = getScheduler()->scheduleIn([this] { updateMappingList(false); },
                                                          MAP_UPDATE_INTERVAL);
-
-    bool hasValidIgd = false;
+    std::shared_ptr<IGD> prefIgd;
     for (auto const& [_, protocol] : protocolList_) {
-        auto valid = protocol->hasValidIgd();
-        if (valid) {
-            JAMI_DBG("Protocol status [%s] has valid IGD(s) %s",
-                     protocol->getProtocolName(),
-                     valid ? "YES" : "NO");
-            hasValidIgd = true;
+        if (protocol->isReady()) {
+            std::list<std::shared_ptr<IGD>> igdList;
+            protocol->getIgdList(igdList);
+            assert(not igdList.empty());
+            auto const& igd = igdList.front();
+
+            // Perefer NAT-PMP if available.
+            if (not prefIgd or protocol->getProtocol() == NatProtocolType::NAT_PMP)
+                prefIgd = igd;
         }
     }
 
-    if (not hasValidIgd) {
+    if (not prefIgd) {
         JAMI_DBG("UPNP/NAT-PMP enabled, but no valid IGDs available");
         std::lock_guard<std::mutex> lock(mappingMutex_);
         // Invalidate the current IGDs.
@@ -517,6 +521,14 @@ UPnPContext::updateMappingList(bool async)
         return;
     }
 
+    JAMI_DBG("Current preferred protocol [%s] IGD [%s %s] ",
+             prefIgd->getProtocolName(),
+             prefIgd->getUID().c_str(),
+             prefIgd->getLocalIp().toString().c_str());
+
+    // Process pending requests if any.
+    processPendingRequests(prefIgd);
+
     // Make new requests for mappings that failed and have
     // the auto-update option enabled.
     processMappingWithAutoUpdate();
@@ -567,10 +579,10 @@ UPnPContext::updateMappingList(bool async)
     }
 
     // Prune the mapping list if needed
-    if (protocolList_.at(NatProtocolType::PUPNP)->hasValidIgd()) {
+    if (protocolList_.at(NatProtocolType::PUPNP)->isReady()) {
 #if HAVE_LIBNATPMP
         // Dont perform if NAT-PMP is valid.
-        if (not protocolList_.at(NatProtocolType::NAT_PMP)->hasValidIgd())
+        if (not protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
 #endif
         {
             pruneMappingList();
@@ -579,7 +591,7 @@ UPnPContext::updateMappingList(bool async)
 
 #if HAVE_LIBNATPMP
     // Renew nat-pmp allocations
-    if (protocolList_.at(NatProtocolType::NAT_PMP)->hasValidIgd())
+    if (protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
         renewAllocations();
 #endif
 }
@@ -809,12 +821,12 @@ UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
 
     // Check if IGD has a valid addresses.
     if (not igdLocalAddr) {
-        JAMI_ERR("[%s] IGD has an invalid local address", protocolName);
+        JAMI_WARN("[%s] IGD has an invalid local address", protocolName);
         return;
     }
 
     if (not igd->getPublicIp()) {
-        JAMI_ERR("[%s] IGD has an invalid public address", protocolName);
+        JAMI_WARN("[%s] IGD has an invalid public address", protocolName);
         return;
     }
 
@@ -826,11 +838,12 @@ UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
                   knownPublicAddress_.toString().c_str());
     }
 
-    char const* IgdState = event == UpnpIgdEvent::ADDED     ? "ADDED"
-                           : event == UpnpIgdEvent::REMOVED ? "REMOVED"
-                                                            : "INVALID";
+    char const* IgdState = event == UpnpIgdEvent::ADDED
+                               ? "ADDED"
+                               : event == UpnpIgdEvent::REMOVED ? "REMOVED" : "INVALID";
 
-    JAMI_WARN("State of IGD %s [%s] changed to [%s]",
+    JAMI_WARN("State of IGD %s %s [%s] changed to [%s]",
+              igd->getUID().c_str(),
               igdLocalAddr.toString(true, true).c_str(),
               protocolName,
               IgdState);
@@ -861,19 +874,6 @@ UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
         }
     }
 
-    // Prune the IGD before making new requests.
-    if (igd->getProtocol() == NatProtocolType::PUPNP) {
-        auto protocol = protocolList_.at(igd->getProtocol());
-        auto remoteMapList
-            = protocol->getMappingsListByDescr(igd, Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
-        if (not remoteMapList.empty()) {
-            pruneUnMatchedMappings(igd, remoteMapList);
-        }
-    }
-
-    // Process pending requests if any.
-    processPendingRequests(igd);
-
     // Update.
     updateMappingList(false);
 }
@@ -883,79 +883,56 @@ UPnPContext::onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& mapR
 {
     CHECK_VALID_THREAD();
 
-    if (not mapRes.isValid() or mapRes.getState() == MappingState::FAILED) {
-        JAMI_WARN("Mapping request %s failed on IGD %s [%s]",
-                  mapRes.toString().c_str(),
-                  igd->getLocalIp().toString().c_str(),
-                  mapRes.getProtocolName());
+    // Check if we have a pending request for this response.
+    auto map = getMappingWithKey(mapRes.getMapKey());
+    if (not map) {
+        // We may receive a response for a canceled request. Just ignore it.
+        JAMI_DBG("Response for mapping %s [IGD %s] [%s] does not have a local match",
+                 mapRes.toString().c_str(),
+                 igd->getLocalIp().toString().c_str(),
+                 mapRes.getProtocolName());
         return;
     }
 
-    Mapping::key_t key = 0;
-
-    {
-        std::lock_guard<std::mutex> lock(mappingMutex_);
-        auto& mappingList = getMappingList(mapRes.getType());
-        auto it = mappingList.find(mapRes.getMapKey());
-        if (it == mappingList.end()) {
-            // We may receive a response for a canceled request. Just
-            // ignore it.
-            JAMI_DBG("Mapping request %s from IGD  %s [%s] does not match local request",
-                     mapRes.toString().c_str(),
-                     igd->getLocalIp().toString().c_str(),
-                     mapRes.getProtocolName());
-            return;
-        }
-        key = it->second->getMapKey();
-        assert(key);
-    }
-
-    auto map = getMappingWithKey(key);
     // The mapping pointer must be valid at his point.
     assert(map);
 
-    // Update the state.
-    if (map->isValid()) {
-        // If both protocols grant the mapping, we prefere using NAT-PMP.
-        if (map->getState() == MappingState::OPEN
-            and map->getProtocol() == NatProtocolType::NAT_PMP) {
-            JAMI_DBG("Mapping %s is already open on IGD %s [NAT-PMP]",
-                     map->toString().c_str(),
-                     igd->getLocalIp().toString().c_str());
-        } else {
-            // Set the attributes
-            map->setExternalAddress(mapRes.getExternalAddress());
-            map->setInternalAddress(mapRes.getInternalAddress());
-            map->setIgd(igd);
-#if HAVE_LIBNATPMP
-            if (mapRes.getProtocol() == NatProtocolType::NAT_PMP) {
-                map->setRenewalTime(mapRes.getRenewalTime());
-            }
-#endif
-            updateMappingState(map, MappingState::OPEN);
+    // We have a response, so cancel the timer.
+    map->cancelTimeoutTimer();
 
-            JAMI_DBG("Mapping %s (on IGD %s [%s]) successfully performed",
-                     map->toString().c_str(),
-                     igd->getLocalIp().toString().c_str(),
-                     map->getProtocolName());
+    // Update the mapping.
+    map->updateFrom(mapRes);
 
-            if (not igd->isValid()) {
-                std::lock_guard<std::mutex> lock(mappingMutex_);
-                validIgdList_.emplace(igd);
-            }
-            // Call setValid() to reset the errors counter.
-            igd->setValid(true);
-        }
-    } else {
-        JAMI_ERR("Failed to perform mapping %s (on IGD %s)",
-                 map->toString().c_str(),
-                 igd->getLocalIp().toString().c_str());
+    // Clean-up if not valid.
+    if (not map->isValid()) {
+        JAMI_WARN("Mapping request %s on IGD %s failed or returned an invalid mapping!",
+                  map->toString(true).c_str(),
+                  igd->getLocalIp().toString().c_str());
         updateMappingState(map, MappingState::FAILED);
         unregisterMapping(map);
+        return;
     }
 
-    // We have a response, so cancel the time-out.
-    map->cancelTimeoutTimer();
+    // Ignore if already open.
+    if (map->getState() == MappingState::OPEN) {
+        JAMI_DBG("Mapping %s is already open on IGD %s [%s]",
+                 map->toString().c_str(),
+                 map->getIgd()->getLocalIp().toString().c_str(),
+                 map->getIgd()->getProtocolName());
+        return;
+    }
+
+    // The mapping request is new and successful. Update.
+    updateMappingState(map, MappingState::OPEN);
+
+    JAMI_DBG("Mapping %s (on IGD %s [%s]) successfully performed",
+             map->toString().c_str(),
+             igd->getLocalIp().toString().c_str(),
+             map->getProtocolName());
+
+    // Call setValid() to reset the errors counter. We need
+    // to reset the counter on each successful response.
+    igd->setValid(true);
 }
 
 #if HAVE_LIBNATPMP
@@ -1072,7 +1049,7 @@ UPnPContext::registerMapping(Mapping& map)
 
     // No available IGD. The pending mapping requests will be processed
     // when a IGD becomes available (in onIgdAdded() method).
-    if (not hasValidIGD()) {
+    if (not isReady()) {
         JAMI_WARN("No IGD available. Mapping will be requested when an IGD becomes available");
     } else {
         requestMappingOnValidIgds(mapPtr);
@@ -1191,9 +1168,12 @@ UPnPContext::registerAddMappingTimeout(const std::shared_ptr<IGD>& igd,
     auto timeout = igd->getProtocol() == NatProtocolType::NAT_PMP ? NAT_MAP_REQUEST_TIMEOUT_UNIT
                                                                   : PUPNP_MAP_REQUEST_TIMEOUT_UNIT;
     map->setTimeoutTimer(
-        // The time-out is set proportional to the number of "in-progress" requests.
+        // The time-out is set proportional to the number of "in-progress"
+        // requests plus a bias.
+        // This rule is empirical and based on experimenting with few
+        // implementations.
         Manager::instance().scheduler().scheduleIn([this, igd, map] { onRequestTimeOut(igd, map); },
-                                                   timeout * (status.inProgressCount_ + 1)));
+                                                   timeout * (status.inProgressCount_ + 3)));
 }
 
 void
@@ -1237,7 +1217,7 @@ UPnPContext::updateMappingState(const Mapping::sharedPtr_t& map, MappingState ne
 
     // Ignore if the state did not change.
     if (newState == map->getState()) {
-        JAMI_WARN("Mapping %s already in state %s", map->toString().c_str(), map->getStateStr());
+        JAMI_DBG("Mapping %s already in state %s", map->toString().c_str(), map->getStateStr());
         return;
     }
 
diff --git a/src/upnp/upnp_context.h b/src/upnp/upnp_context.h
index 566e87b61da7cf85844b28d52722a6b120d8e037..080dc83cd3045d4c7a322adcd9f922789d756ead 100644
--- a/src/upnp/upnp_context.h
+++ b/src/upnp/upnp_context.h
@@ -109,7 +109,7 @@ public:
     void setPublicAddress(const IpAddr& addr);
 
     // Check if there is a valid IGD in the IGD list.
-    bool hasValidIGD() const;
+    bool isReady() const;
 
     // Get external Ip of a chosen IGD.
     IpAddr getExternalIP() const;
diff --git a/src/upnp/upnp_control.cpp b/src/upnp/upnp_control.cpp
index 72048aa23dffec5803dcf9d36426acef58f6c51e..652efe4486983d802fb1fb506c303847c1c362f8 100644
--- a/src/upnp/upnp_control.cpp
+++ b/src/upnp/upnp_control.cpp
@@ -57,17 +57,17 @@ Controller::~Controller()
 }
 
 bool
-Controller::hasValidIGD() const
+Controller::isReady() const
 {
     assert(upnpContext_);
-    return upnpContext_->hasValidIGD();
+    return upnpContext_->isReady();
 }
 
 IpAddr
 Controller::getExternalIP() const
 {
     assert(upnpContext_);
-    if (upnpContext_->hasValidIGD()) {
+    if (upnpContext_->isReady()) {
         return upnpContext_->getExternalIP();
     }
     return {};
diff --git a/src/upnp/upnp_control.h b/src/upnp/upnp_control.h
index 87714f12e8cbf353642d71994dee30811fda191c..67218d37a25cea92f2c2bdb38b28142d735d4da9 100644
--- a/src/upnp/upnp_control.h
+++ b/src/upnp/upnp_control.h
@@ -51,7 +51,7 @@ public:
     ~Controller();
 
     // Checks if a valid IGD is available.
-    bool hasValidIGD() const;
+    bool isReady() const;
 
     // Gets the external ip of the first valid IGD in the list.
     IpAddr getExternalIP() const;