diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py
index d5be0757cf3..439c3a8760b 100644
--- a/homeassistant/components/upnp/__init__.py
+++ b/homeassistant/components/upnp/__init__.py
@@ -21,7 +21,6 @@ from .const import (
     DISCOVERY_UDN,
     DOMAIN,
     DOMAIN_CONFIG,
-    DOMAIN_COORDINATORS,
     DOMAIN_DEVICES,
     DOMAIN_LOCAL_IP,
     LOGGER as _LOGGER,
@@ -75,7 +74,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType):
     local_ip = await hass.async_add_executor_job(get_local_ip)
     hass.data[DOMAIN] = {
         DOMAIN_CONFIG: conf,
-        DOMAIN_COORDINATORS: {},
         DOMAIN_DEVICES: {},
         DOMAIN_LOCAL_IP: conf.get(CONF_LOCAL_IP, local_ip),
     }
@@ -149,6 +147,9 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry)
         hass.config_entries.async_forward_entry_setup(config_entry, "sensor")
     )
 
+    # Start device updater.
+    await device.async_start()
+
     return True
 
 
@@ -160,9 +161,10 @@ async def async_unload_entry(
 
     udn = config_entry.data.get(CONFIG_ENTRY_UDN)
     if udn in hass.data[DOMAIN][DOMAIN_DEVICES]:
+        device = hass.data[DOMAIN][DOMAIN_DEVICES][udn]
+        await device.async_stop()
+
         del hass.data[DOMAIN][DOMAIN_DEVICES][udn]
-    if udn in hass.data[DOMAIN][DOMAIN_COORDINATORS]:
-        del hass.data[DOMAIN][DOMAIN_COORDINATORS][udn]
 
     _LOGGER.debug("Deleting sensors")
     return await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py
index 1cbaf931857..7a1a3d4a06c 100644
--- a/homeassistant/components/upnp/config_flow.py
+++ b/homeassistant/components/upnp/config_flow.py
@@ -25,7 +25,7 @@ from .const import (
     DISCOVERY_UNIQUE_ID,
     DISCOVERY_USN,
     DOMAIN,
-    DOMAIN_COORDINATORS,
+    DOMAIN_DEVICES,
     LOGGER as _LOGGER,
 )
 from .device import Device
@@ -252,7 +252,7 @@ class UpnpOptionsFlowHandler(config_entries.OptionsFlow):
         """Manage the options."""
         if user_input is not None:
             udn = self.config_entry.data[CONFIG_ENTRY_UDN]
-            coordinator = self.hass.data[DOMAIN][DOMAIN_COORDINATORS][udn]
+            coordinator = self.hass.data[DOMAIN][DOMAIN_DEVICES][udn].coordinator
             update_interval_sec = user_input.get(
                 CONFIG_ENTRY_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
             )
diff --git a/homeassistant/components/upnp/const.py b/homeassistant/components/upnp/const.py
index 6575139c4a4..142524ef9ca 100644
--- a/homeassistant/components/upnp/const.py
+++ b/homeassistant/components/upnp/const.py
@@ -9,7 +9,6 @@ LOGGER = logging.getLogger(__package__)
 CONF_LOCAL_IP = "local_ip"
 DOMAIN = "upnp"
 DOMAIN_CONFIG = "config"
-DOMAIN_COORDINATORS = "coordinators"
 DOMAIN_DEVICES = "devices"
 DOMAIN_LOCAL_IP = "local_ip"
 BYTES_RECEIVED = "bytes_received"
diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py
index 034496ec028..aafd9f51516 100644
--- a/homeassistant/components/upnp/device.py
+++ b/homeassistant/components/upnp/device.py
@@ -8,10 +8,12 @@ from urllib.parse import urlparse
 
 from async_upnp_client import UpnpFactory
 from async_upnp_client.aiohttp import AiohttpSessionRequester
+from async_upnp_client.device_updater import DeviceUpdater
 from async_upnp_client.profiles.igd import IgdDevice
 
 from homeassistant.helpers.aiohttp_client import async_get_clientsession
 from homeassistant.helpers.typing import HomeAssistantType
+from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
 import homeassistant.util.dt as dt_util
 
 from .const import (
@@ -34,23 +36,29 @@ from .const import (
 )
 
 
+def _get_local_ip(hass: HomeAssistantType) -> IPv4Address | None:
+    """Get the configured local ip."""
+    if DOMAIN in hass.data and DOMAIN_CONFIG in hass.data[DOMAIN]:
+        local_ip = hass.data[DOMAIN][DOMAIN_CONFIG].get(CONF_LOCAL_IP)
+        if local_ip:
+            return IPv4Address(local_ip)
+    return None
+
+
 class Device:
     """Home Assistant representation of a UPnP/IGD device."""
 
-    def __init__(self, igd_device):
+    def __init__(self, igd_device: IgdDevice, device_updater: DeviceUpdater) -> None:
         """Initialize UPnP/IGD device."""
-        self._igd_device: IgdDevice = igd_device
+        self._igd_device = igd_device
+        self._device_updater = device_updater
+        self.coordinator: DataUpdateCoordinator = None
 
     @classmethod
     async def async_discover(cls, hass: HomeAssistantType) -> list[Mapping]:
         """Discover UPnP/IGD devices."""
         _LOGGER.debug("Discovering UPnP/IGD devices")
-        local_ip = None
-        if DOMAIN in hass.data and DOMAIN_CONFIG in hass.data[DOMAIN]:
-            local_ip = hass.data[DOMAIN][DOMAIN_CONFIG].get(CONF_LOCAL_IP)
-        if local_ip:
-            local_ip = IPv4Address(local_ip)
-
+        local_ip = _get_local_ip(hass)
         discoveries = await IgdDevice.async_search(source_ip=local_ip, timeout=10)
 
         # Supplement/standardize discovery.
@@ -81,17 +89,32 @@ class Device:
         cls, hass: HomeAssistantType, ssdp_location: str
     ) -> Device:
         """Create UPnP/IGD device."""
-        # build async_upnp_client requester
+        # Build async_upnp_client requester.
         session = async_get_clientsession(hass)
         requester = AiohttpSessionRequester(session, True, 10)
 
-        # create async_upnp_client device
+        # Create async_upnp_client device.
         factory = UpnpFactory(requester, disable_state_variable_validation=True)
         upnp_device = await factory.async_create_device(ssdp_location)
 
+        # Create profile wrapper.
         igd_device = IgdDevice(upnp_device, None)
 
-        return cls(igd_device)
+        # Create updater.
+        local_ip = _get_local_ip(hass)
+        device_updater = DeviceUpdater(
+            device=upnp_device, factory=factory, source_ip=local_ip
+        )
+
+        return cls(igd_device, device_updater)
+
+    async def async_start(self) -> None:
+        """Start the device updater."""
+        await self._device_updater.async_start()
+
+    async def async_stop(self) -> None:
+        """Stop the device updater."""
+        await self._device_updater.async_stop()
 
     @property
     def udn(self) -> str:
diff --git a/homeassistant/components/upnp/sensor.py b/homeassistant/components/upnp/sensor.py
index 0e95b6106a3..d144bd29299 100644
--- a/homeassistant/components/upnp/sensor.py
+++ b/homeassistant/components/upnp/sensor.py
@@ -2,7 +2,7 @@
 from __future__ import annotations
 
 from datetime import timedelta
-from typing import Any, Mapping
+from typing import Any, Callable, Mapping
 
 from homeassistant.components.sensor import SensorEntity
 from homeassistant.config_entries import ConfigEntry
@@ -23,7 +23,6 @@ from .const import (
     DATA_RATE_PACKETS_PER_SECOND,
     DEFAULT_SCAN_INTERVAL,
     DOMAIN,
-    DOMAIN_COORDINATORS,
     DOMAIN_DEVICES,
     KIBIBYTE,
     LOGGER as _LOGGER,
@@ -83,7 +82,7 @@ async def async_setup_platform(
 
 
 async def async_setup_entry(
-    hass, config_entry: ConfigEntry, async_add_entities
+    hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable
 ) -> None:
     """Set up the UPnP/IGD sensors."""
     udn = config_entry.data[CONFIG_ENTRY_UDN]
@@ -102,8 +101,9 @@ async def async_setup_entry(
         update_method=device.async_get_traffic_data,
         update_interval=update_interval,
     )
+    device.coordinator = coordinator
+
     await coordinator.async_refresh()
-    hass.data[DOMAIN][DOMAIN_COORDINATORS][udn] = coordinator
 
     sensors = [
         RawUpnpSensor(coordinator, device, SENSOR_TYPES[BYTES_RECEIVED]),
@@ -126,14 +126,11 @@ class UpnpSensor(CoordinatorEntity, SensorEntity):
         coordinator: DataUpdateCoordinator[Mapping[str, Any]],
         device: Device,
         sensor_type: Mapping[str, str],
-        update_multiplier: int = 2,
     ) -> None:
         """Initialize the base sensor."""
         super().__init__(coordinator)
         self._device = device
         self._sensor_type = sensor_type
-        self._update_counter_max = update_multiplier
-        self._update_counter = 0
 
     @property
     def icon(self) -> str:
diff --git a/tests/components/upnp/mock_device.py b/tests/components/upnp/mock_device.py
index d6027608137..d2ef9ad41e3 100644
--- a/tests/components/upnp/mock_device.py
+++ b/tests/components/upnp/mock_device.py
@@ -1,6 +1,7 @@
 """Mock device for testing purposes."""
 
 from typing import Mapping
+from unittest.mock import AsyncMock
 
 from homeassistant.components.upnp.const import (
     BYTES_RECEIVED,
@@ -10,7 +11,7 @@ from homeassistant.components.upnp.const import (
     TIMESTAMP,
 )
 from homeassistant.components.upnp.device import Device
-import homeassistant.util.dt as dt_util
+from homeassistant.util import dt
 
 
 class MockDevice(Device):
@@ -19,8 +20,10 @@ class MockDevice(Device):
     def __init__(self, udn: str) -> None:
         """Initialize mock device."""
         igd_device = object()
-        super().__init__(igd_device)
+        mock_device_updater = AsyncMock()
+        super().__init__(igd_device, mock_device_updater)
         self._udn = udn
+        self.times_polled = 0
 
     @classmethod
     async def async_create_device(cls, hass, ssdp_location) -> "MockDevice":
@@ -59,8 +62,9 @@ class MockDevice(Device):
 
     async def async_get_traffic_data(self) -> Mapping[str, any]:
         """Get traffic data."""
+        self.times_polled += 1
         return {
-            TIMESTAMP: dt_util.utcnow(),
+            TIMESTAMP: dt.utcnow(),
             BYTES_RECEIVED: 0,
             BYTES_SENT: 0,
             PACKETS_RECEIVED: 0,
diff --git a/tests/components/upnp/test_config_flow.py b/tests/components/upnp/test_config_flow.py
index 77d04381a12..facc5f05701 100644
--- a/tests/components/upnp/test_config_flow.py
+++ b/tests/components/upnp/test_config_flow.py
@@ -19,15 +19,15 @@ from homeassistant.components.upnp.const import (
     DISCOVERY_UNIQUE_ID,
     DISCOVERY_USN,
     DOMAIN,
-    DOMAIN_COORDINATORS,
 )
 from homeassistant.components.upnp.device import Device
 from homeassistant.helpers.typing import HomeAssistantType
 from homeassistant.setup import async_setup_component
+from homeassistant.util import dt
 
 from .mock_device import MockDevice
 
-from tests.common import MockConfigEntry
+from tests.common import MockConfigEntry, async_fire_time_changed
 
 
 async def test_flow_ssdp_discovery(hass: HomeAssistantType):
@@ -325,10 +325,12 @@ async def test_options_flow(hass: HomeAssistantType):
         # Initialisation of component.
         await async_setup_component(hass, "upnp", config)
         await hass.async_block_till_done()
+        mock_device.times_polled = 0  # Reset.
 
-        # DataUpdateCoordinator gets a default of 30 seconds for updates.
-        coordinator = hass.data[DOMAIN][DOMAIN_COORDINATORS][mock_device.udn]
-        assert coordinator.update_interval == timedelta(seconds=DEFAULT_SCAN_INTERVAL)
+        # Forward time, ensure single poll after 30 (default) seconds.
+        async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=31))
+        await hass.async_block_till_done()
+        assert mock_device.times_polled == 1
 
         # Options flow with no input results in form.
         result = await hass.config_entries.options.async_init(
@@ -346,5 +348,18 @@ async def test_options_flow(hass: HomeAssistantType):
             CONFIG_ENTRY_SCAN_INTERVAL: 60,
         }
 
-        # Also updates DataUpdateCoordinator.
-        assert coordinator.update_interval == timedelta(seconds=60)
+        # Forward time, ensure single poll after 60 seconds, still from original setting.
+        async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=61))
+        await hass.async_block_till_done()
+        assert mock_device.times_polled == 2
+
+        # Now the updated interval takes effect.
+        # Forward time, ensure single poll after 120 seconds.
+        async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=121))
+        await hass.async_block_till_done()
+        assert mock_device.times_polled == 3
+
+        # Forward time, ensure single poll after 180 seconds.
+        async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=181))
+        await hass.async_block_till_done()
+        assert mock_device.times_polled == 4