diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c
index 1e6638b..403ba2f 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -733,6 +733,7 @@
     }
     ASSERT(TUNNEL_TYPE(c->c1.tuntap) == DEV_TYPE_TUN);
 
+    msg(D_DCO, "DCO: attempt removing iroutes from system table");
 
     if (c->c2.push_ifconfig_defined)
     {
diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
index 27cfd36..6c5ab4d 100644
--- a/src/openvpn/forward.c
+++ b/src/openvpn/forward.c
@@ -525,7 +525,7 @@
  * Schedule a SIGTERM signal c->options.scheduled_exit_interval seconds from now.
  */
 bool
-schedule_exit(struct context *c)
+schedule_exit(openvpn_net_ctx_t *net_ctx, struct context *c)
 {
     const int n_seconds = c->options.scheduled_exit_interval;
     /* don't reschedule if already scheduled. */
@@ -533,6 +533,23 @@
     {
         return false;
     }
+
+    /* DCO iroutes must be removed now, because the delay introduced by this
+     * timer can create a race condition:
+     * the same client may reconnect before the old instance is purged, leading
+     * to DCO iroutes removal *after* reconnection, thus killing the routes
+     * for the new instance too.
+     *
+     * Standard/virtual iroutes (non-DCO case) are not affected because the
+     * last connecting client claiming the iroutes takes ownership. Therefore
+     * they are not removed during delayed cleanup.
+     */
+    if (c->did_dco_iroutes)
+    {
+        c->did_dco_iroutes = false;
+        dco_delete_iroutes(net_ctx, c);
+    }
+
     tls_set_single_session(c->c2.tls_multi);
     update_time();
     reset_coarse_timers(c);
diff --git a/src/openvpn/forward.h b/src/openvpn/forward.h
index 0d3e492..374e1df 100644
--- a/src/openvpn/forward.h
+++ b/src/openvpn/forward.h
@@ -331,7 +331,7 @@
 void process_ip_header(struct context *c, unsigned int flags, struct buffer *buf,
                        struct link_socket *sock);
 
-bool schedule_exit(struct context *c);
+bool schedule_exit(openvpn_net_ctx_t *net_ctx, struct context *c);
 
 static inline struct link_socket_info *
 get_link_socket_info(struct context *c)
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index a72dcd1..ac9b986 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -479,7 +479,12 @@
     const struct iroute *ir;
     const struct iroute_ipv6 *ir6;
 
-    dco_delete_iroutes(&m->top.net_ctx, &mi->context);
+    /* check if DCO iroutes were already removed when scheduling a delayed exit */
+    if (mi->context.did_dco_iroutes)
+    {
+        mi->context.did_dco_iroutes = false;
+        dco_delete_iroutes(&m->top.net_ctx, &mi->context);
+    }
 
     if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)
     {
@@ -1278,6 +1283,8 @@
     if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)
     {
         mi->did_iroutes = true;
+        /* multi_learn_in{6}_addr_t takes care of installing the DCO iroute */
+        mi->context.did_dco_iroutes = true;
         for (ir = mi->context.options.iroutes; ir != NULL; ir = ir->next)
         {
             if (ir->netbits >= 0)
diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h
index fa00822..5747175 100644
--- a/src/openvpn/openvpn.h
+++ b/src/openvpn/openvpn.h
@@ -507,6 +507,8 @@
     bool did_we_daemonize;       /**< Whether demonization has already
                                   *   taken place. */
 
+    bool did_dco_iroutes;        /**< Whether DCO iroutes have been installed */
+
     struct context_persist persist;
     /**< Persistent %context. */
     struct context_0 *c0; /**< Level 0 %context. */
diff --git a/src/openvpn/push.c b/src/openvpn/push.c
index 564ce86..50053e9 100644
--- a/src/openvpn/push.c
+++ b/src/openvpn/push.c
@@ -202,7 +202,7 @@
      * */
     if (c->options.mode == MODE_SERVER)
     {
-        if (!schedule_exit(c))
+        if (!schedule_exit(&c->net_ctx, c))
         {
             /* Return early when we don't need to notify management */
             return;
@@ -392,7 +392,7 @@
 void
 send_auth_failed(struct context *c, const char *client_reason)
 {
-    if (!schedule_exit(c))
+    if (!schedule_exit(&c->net_ctx, c))
     {
         msg(D_TLS_DEBUG, "exit already scheduled for context");
         return;
@@ -493,7 +493,7 @@
 void
 send_restart(struct context *c, const char *kill_msg)
 {
-    schedule_exit(c);
+    schedule_exit(&c->net_ctx, c);
     send_control_channel_string(c, kill_msg ? kill_msg : "RESTART", D_PUSH);
 }
 
