diff --git a/include/upnp/upnp_context.h b/include/upnp/upnp_context.h
index d16105372438f9873846dcbdb830b154548f7f98..e29e05bdf24a6bda9c423236d7004b5b7840abcb 100644
--- a/include/upnp/upnp_context.h
+++ b/include/upnp/upnp_context.h
@@ -105,7 +105,6 @@ public:
     Mapping::sharedPtr_t reserveMapping(Mapping& requestedMap);
 
     // Release a used mapping (make it available for future use).
-    // TODO: The current implementation doesn't seem to do the "make it available for future use" part... fix this.
     void releaseMapping(const Mapping& map);
 
     // Register a controller
diff --git a/src/upnp/upnp_context.cpp b/src/upnp/upnp_context.cpp
index cf9b6ed3ddd501bb1b389225247b5464966f1e4e..cffab9987df22b665fa806369adcf2f45630a913 100644
--- a/src/upnp/upnp_context.cpp
+++ b/src/upnp/upnp_context.cpp
@@ -399,9 +399,14 @@ UPnPContext::releaseMapping(const Mapping& map)
             return;
         }
 
-        // Remove it.
-        requestRemoveMapping(mapPtr);
-        unregisterMapping(mapPtr, true);
+        // Reset the mapping options: disable auto-update and remove the notify callback.
+        // This is important because the mapping will be available again and can be reused
+        // by another (or the same) controller which may have different preferences.
+        // The notify callback is also removed to avoid calling it when the mapping is not used anymore.
+        mapPtr->setNotifyCallback(nullptr);
+        mapPtr->enableAutoUpdate(false);
+        mapPtr->setAvailable(true);
+        if (logger_) logger_->debug("Mapping {} released", mapPtr->toString());
         enforceAvailableMappingsLimits();
     });
 }
@@ -620,7 +625,7 @@ UPnPContext::enforceAvailableMappingsLimits()
     // If there is no valid IGD, do nothing.
     if (!isReady())
         return;
-        
+
     for (auto type : {PortType::TCP, PortType::UDP}) {
         int pendingCount = 0;
         int inProgressCount = 0;