diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index a850e72..8a4b2e1 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -4496,6 +4496,7 @@
 static void
 unlearn_ifconfig(struct multi_context *m, struct multi_instance *mi)
 {
+    multi_del_dco_iroutes_v4(m, mi);
     in_addr_t old_addr = 0;
     old_addr = htonl(mi->context.c2.push_ifconfig_local);
     multi_unlearn_in_addr_t(m, mi, old_addr);
@@ -4508,6 +4509,7 @@
 static void
 unlearn_ifconfig_ipv6(struct multi_context *m, struct multi_instance *mi)
 {
+    multi_del_dco_iroutes_v6(m, mi);
     struct in6_addr old_addr6;
     CLEAR(old_addr6);
     old_addr6 = mi->context.c2.push_ifconfig_ipv6_local;
@@ -4517,9 +4519,54 @@
     CLEAR(mi->reporting_addr_ipv6);
 }
 
+/* Reinstall only routes that are still owned by this client. */
+static void
+multi_reinstall_owned_dco_iroute(struct multi_context *m, struct multi_instance *mi,
+                                 const struct mroute_addr *addr)
+{
+    const struct multi_route *route = hash_lookup(m->vhash, addr);
+    if (route && route->instance == mi && multi_route_defined(m, route))
+    {
+        dco_install_iroute(m, mi, addr);
+    }
+}
+
+/* Userspace iroutes do not depend on the client's VPN address. Only their
+ * DCO routes need to be reinstalled when the gateway changes. */
+static void
+multi_reinstall_dco_iroutes(struct multi_context *m, struct multi_instance *mi, int flags)
+{
+    if ((flags & (1 << AF_INET)) && mi->context.c2.push_ifconfig_defined)
+    {
+        for (const struct iroute *ir = mi->context.options.iroutes; ir; ir = ir->next)
+        {
+            struct mroute_addr addr = { 0 };
+            mroute_extract_in_addr_t(&addr, ir->network);
+            addr.type |= MR_WITH_NETBITS;
+            addr.netbits = (uint8_t)ir->netbits;
+            multi_reinstall_owned_dco_iroute(m, mi, &addr);
+        }
+    }
+
+    if ((flags & (1 << AF_INET6)) && mi->context.c2.push_ifconfig_ipv6_defined)
+    {
+        for (const struct iroute_ipv6 *ir6 = mi->context.options.iroutes_ipv6; ir6; ir6 = ir6->next)
+        {
+            struct mroute_addr addr = { 0 };
+            addr.type = MR_ADDR_IPV6 | MR_WITH_NETBITS;
+            addr.len = 16;
+            addr.netbits = (uint8_t)ir6->netbits;
+            addr.v6.addr = ir6->network;
+            mroute_addr_mask_host_bits(&addr);
+            multi_reinstall_owned_dco_iroute(m, mi, &addr);
+        }
+    }
+}
+
 /**
- * Update the vhash when a push-update message containing
- * ifconfig/ifconfig-ipv6 options is sent from the server.
+ * Update the vhash and DCO peer addresses when a push-update message containing
+ * ifconfig/ifconfig-ipv6 options is sent from the server. Preserve userspace
+ * iroutes and reinstall their DCO routes with the new IP if needed.
  *
  * @param m         The multi_context
  * @param mi        The multi_instance of the client we are updating
@@ -4531,15 +4578,20 @@
 bool
 update_vaddr(struct multi_context *m, struct multi_instance *mi, const char *new_ip, const char *new_ipv6)
 {
+    /* Track changed address families to avoid resetting unchanged DCO addresses. */
+    int update_flags = 0;
+
     /* -ifconfig */
     if (!new_ip && mi->context.c2.push_ifconfig_defined)
     {
         unlearn_ifconfig(m, mi);
+        update_flags |= (1 << AF_INET);
     }
     /* -ifconfig-ipv6 */
     if (!new_ipv6 && mi->context.c2.push_ifconfig_ipv6_defined)
     {
         unlearn_ifconfig_ipv6(m, mi);
+        update_flags |= (1 << AF_INET6);
     }
 
     if (new_ip)
@@ -4553,6 +4605,7 @@
             if (mi->context.c2.push_ifconfig_defined)
             {
                 unlearn_ifconfig(m, mi);
+                update_flags |= (1 << AF_INET);
             }
 
             /* DCO needs the new address while installing its on-link route. */
@@ -4562,6 +4615,7 @@
             {
                 /* set our client's VPN endpoint for status reporting purposes */
                 mi->reporting_addr = mi->context.c2.push_ifconfig_local;
+                update_flags |= (1 << AF_INET);
             }
             else
             {
@@ -4589,6 +4643,7 @@
             if (mi->context.c2.push_ifconfig_ipv6_defined)
             {
                 unlearn_ifconfig_ipv6(m, mi);
+                update_flags |= (1 << AF_INET6);
             }
 
             /* DCO needs the new address while installing its on-link route. */
@@ -4598,6 +4653,7 @@
             {
                 /* set our client's VPN endpoint for status reporting purposes */
                 mi->reporting_addr_ipv6 = mi->context.c2.push_ifconfig_ipv6_local;
+                update_flags |= (1 << AF_INET6);
             }
             else
             {
@@ -4613,6 +4669,24 @@
             }
         }
     }
+
+    if (!update_flags || !dco_enabled(&m->top.options))
+    {
+        return true;
+    }
+
+    int ret = dco_multi_update_peer_addr(&mi->context, update_flags);
+    if (ret < 0)
+    {
+        msg(M_WARN, "MULTI: failed to update DCO peer addresses (error %d)", ret);
+        /* PUSH_UPDATE is already queued, so reconnect to restore consistent state. */
+        send_restart(&mi->context, NULL);
+        multi_schedule_context_wakeup(m, mi);
+        return false;
+    }
+
+    multi_reinstall_dco_iroutes(m, mi, update_flags);
+
     return true;
 }
 
diff --git a/src/openvpn/push_util.c b/src/openvpn/push_util.c
index cd940fb..aa77583 100644
--- a/src/openvpn/push_util.c
+++ b/src/openvpn/push_util.c
@@ -255,12 +255,14 @@
 static int
 send_push_update(struct multi_context *m, const void *target, const char *msg, const push_update_type type, const size_t push_bundle_size)
 {
+#if !defined(TARGET_LINUX)
     if (dco_enabled(&m->top.options))
     {
         msg(M_WARN, "WARN: PUSH_UPDATE messages cannot currently be sent while DCO is enabled."
                     " To send a PUSH_UPDATE message, be sure to use the --disable-dco option.");
         return 0;
     }
+#endif
 
     if (!msg || !*msg || !m || (!target && type != UPT_BROADCAST))
     {
@@ -303,6 +305,7 @@
         }
 
         if (!mi->halt
+            && !event_timeout_defined(&mi->context.c2.scheduled_exit)
             && send_single_push_update(m, mi, msgs))
         {
             buffer_list_free(msgs);
@@ -323,7 +326,9 @@
     {
         struct multi_instance *curr_mi = m->instances[i];
 
-        if (!curr_mi || curr_mi->halt || !support_push_update(curr_mi))
+        if (!curr_mi || curr_mi->halt
+            || event_timeout_defined(&curr_mi->context.c2.scheduled_exit)
+            || !support_push_update(curr_mi))
         {
             continue;
         }
