diff --git a/meta/recipes-core/systemd/systemd/0023-network-introduce-link_is_up-helper-function.patch b/meta/recipes-core/systemd/systemd/0023-network-introduce-link_is_up-helper-function.patch
new file mode 100644
index 0000000000..04f7963a8a
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/0023-network-introduce-link_is_up-helper-function.patch
@@ -0,0 +1,166 @@
+From ec4f646759d6625bbafcd4ce67224fb63b99a96c Mon Sep 17 00:00:00 2001
+From: Yu Watanabe <watanabe.yu+github@gmail.com>
+Date: Mon, 9 Mar 2026 10:57:06 +0900
+Subject: [PATCH] network: introduce link_is_up() helper function
+
+Upstream-Status: Backport [https://github.com/systemd/systemd/pull/41000/commits/ec4f646759d6625bbafcd4ce67224fb63b99a96c]
+Comment: Patch is refreshed based on the 255.22 codebase
+Did not pick changes from the below files as it is not matches to our code base.
+src/network/netdev/bond.c
+src/network/networkd-route.c
+
+Signed-off-by: Akash Hadke <Akash.Hadke@bmwtechworks.in>
+---
+ src/network/networkd-link.c    | 21 +++++++++++----------
+ src/network/networkd-link.h    |  1 +
+ src/network/networkd-nexthop.c |  4 ++--
+ src/network/networkd-route.c   |  2 +-
+ src/network/networkd-setlink.c | 11 +++++------
+ 6 files changed, 21 insertions(+), 20 deletions(-)
+
+diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
+index e49d5946aa67d..c77b02b8e0192 100644
+--- a/src/network/networkd-link.c
++++ b/src/network/networkd-link.c
+@@ -726,7 +726,6 @@ int link_ipv6ll_gained(Link *link) {
+
+ int link_handle_bound_to_list(Link *link) {
+         bool required_up = false;
+-        bool link_is_up = false;
+         Link *l;
+
+         assert(link);
+@@ -737,18 +736,15 @@ int link_handle_bound_to_list(Link *link) {
+         if (hashmap_isempty(link->bound_to_links))
+                 return 0;
+
+-        if (link->flags & IFF_UP)
+-                link_is_up = true;
+-
+         HASHMAP_FOREACH(l, link->bound_to_links)
+                 if (link_has_carrier(l)) {
+                         required_up = true;
+                         break;
+                 }
+
+-        if (!required_up && link_is_up)
++        if (!required_up && link_is_up(link))
+                 return link_request_to_bring_up_or_down(link, /* up = */ false);
+-        if (required_up && !link_is_up)
++        if (required_up && !link_is_up(link))
+                 return link_request_to_bring_up_or_down(link, /* up = */ true);
+
+         return 0;
+@@ -1736,7 +1732,7 @@ void link_update_operstate(Link *link, bool also_update_master) {
+                         carrier_state = LINK_CARRIER_STATE_ENSLAVED;
+                 else
+                         carrier_state = LINK_CARRIER_STATE_CARRIER;
+-        } else if (link->flags & IFF_UP)
++        } else if (link_is_up(link))
+                 carrier_state = LINK_CARRIER_STATE_NO_CARRIER;
+         else
+                 carrier_state = LINK_CARRIER_STATE_OFF;
+@@ -1867,6 +1863,11 @@ void link_update_operstate(Link *link, bool also_update_master) {
+          ? ((old & flag) ? (" -" string) : (" +" string))        \
+          : "")
+
++bool link_is_up(Link *link) {
++        assert(link);
++        return FLAGS_SET(link->flags, IFF_UP);
++}
++
+ static int link_update_flags(Link *link, sd_netlink_message *message) {
+         bool link_was_admin_up, had_carrier;
+         uint8_t operstate;
+@@ -1930,21 +1931,21 @@ static int link_update_flags(Link *link, sd_netlink_message *message) {
+                         log_link_debug(link, "Unknown link flags lost, ignoring: %#.5x", unknown_flags_removed);
+         }
+
+-        link_was_admin_up = link->flags & IFF_UP;
++        link_was_admin_up = link_is_up(link);
+         had_carrier = link_has_carrier(link);
+
+         link->flags = flags;
+         link->kernel_operstate = operstate;
+
+         link_update_operstate(link, true);
+-
+-        if (!link_was_admin_up && (link->flags & IFF_UP)) {
++	
++        if (!link_was_admin_up && link_is_up(link)) {
+                 log_link_info(link, "Link UP");
+
+                 r = link_admin_state_up(link);
+                 if (r < 0)
+                         return r;
+-        } else if (link_was_admin_up && !(link->flags & IFF_UP)) {
++        } else if (link_was_admin_up && !link_is_up(link)) {
+                 log_link_info(link, "Link DOWN");
+
+                 r = link_admin_state_down(link);
+diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h
+index c5b9421bc0b2b..9c641fad39bdb 100644
+--- a/src/network/networkd-link.h
++++ b/src/network/networkd-link.h
+@@ -233,6 +233,7 @@ static inline bool link_has_carrier(Link *link) {
+         assert(link);
+         return netif_has_carrier(link->kernel_operstate, link->flags);
+ }
++bool link_is_up(Link *link);
+
+ bool link_ipv6_enabled(Link *link);
+ int link_ipv6ll_gained(Link *link);
+diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c
+index 5e5f0d0e6858b..23941527cfdcf 100644
+--- a/src/network/networkd-nexthop.c
++++ b/src/network/networkd-nexthop.c
+@@ -526,7 +526,7 @@ static bool nexthop_is_ready_to_configur
+                  * kernel. */
+                 if (link->set_flags_messages > 0)
+                         return false;
+-                if (!FLAGS_SET(link->flags, IFF_UP))
++                if (!link_is_up(link))
+                         return false;
+         }
+ 
+diff --git a/src/network/networkd-setlink.c b/src/network/networkd-setlink.c
+index 7069b101f9f55..f5a43788ef792 100644
+--- a/src/network/networkd-setlink.c
++++ b/src/network/networkd-setlink.c
+@@ -486,7 +486,7 @@ static int link_is_ready_to_set_link(Lin
+         case REQUEST_TYPE_SET_LINK_CAN:
+                 /* Do not check link->set_flags_messages here, as it is ok even if link->flags
+                  * is outdated, and checking the counter causes a deadlock. */
+-                if (FLAGS_SET(link->flags, IFF_UP)) {
++                if (link_is_up(link)) {
+                         /* The CAN interface must be down to configure bitrate, etc... */
+                         r = link_down_now(link);
+                         if (r < 0)
+@@ -550,7 +550,7 @@ static int link_is_ready_to_set_link(Lin
+ 
+                 /* Do not check link->set_flags_messages here, as it is ok even if link->flags is outdated,
+                  * and checking the counter causes a deadlock. */
+-                if (link->network->bond && FLAGS_SET(link->flags, IFF_UP)) {
++                if (link->network->bond && link_is_up(link)) {
+                         /* link must be down when joining to bond master. */
+                         r = link_down_now(link);
+                         if (r < 0)
+@@ -649,8 +649,7 @@ int link_request_to_set_addrgen_mode(Lin
+          * link goes down. Hence, we need to reset the interface. However, setting the mode by sysctl
+          * does not need that. Let's use the sysctl interface when the link is already up.
+          * See also issue #22424. */
+-        if (mode != IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_NONE &&
+-            FLAGS_SET(link->flags, IFF_UP)) {
++        if (mode != IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_NONE && link_is_up(link)) {
+                 r = link_set_ipv6ll_addrgen_mode(link, mode);
+                 if (r < 0)
+                         log_link_warning_errno(link, r, "Cannot set IPv6 address generation mode, ignoring: %m");
+@@ -1142,7 +1141,7 @@ static bool link_is_ready_to_bring_up_or
+                 if (link_get_by_index(link->manager, link->dsa_master_ifindex, &master) < 0)
+                         return false;
+ 
+-                if (!FLAGS_SET(master->flags, IFF_UP))
++                if (!link_is_up(master)) 
+                         return false;
+         }
+ 
diff --git a/meta/recipes-core/systemd/systemd/0024-network-check-if-gateway-is-ready-only-when-the-nexthop-is-bound-to-link.patch b/meta/recipes-core/systemd/systemd/0024-network-check-if-gateway-is-ready-only-when-the-nexthop-is-bound-to-link.patch
new file mode 100644
index 0000000000..148a3c2d07
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/0024-network-check-if-gateway-is-ready-only-when-the-nexthop-is-bound-to-link.patch
@@ -0,0 +1,72 @@
+From 43989de6427954f0755e2667bda1cf3839d27964 Mon Sep 17 00:00:00 2001
+From: Yu Watanabe <watanabe.yu+github@gmail.com>
+Date: Mon, 9 Mar 2026 11:30:54 +0900
+Subject: [PATCH] network: check if gateway is ready only when the nexthop is
+ bound to link
+
+Currently, we support three types of nexthop:
+1. simple nexthop, which is bound to link, may have specific gateway
+   address,
+2. blackhole nexthop, which is global configuration and is not bound to
+   any links,
+3. group nexthop, which is also global configuration and is not bound to
+   any links.
+
+Thus, gateway_is_ready() is only necessary to call for simple nexthop
+case. Let's make the logic simpler.
+
+Upstream-Status: Backport [https://github.com/systemd/systemd/commit/43989de6427954f0755e2667bda1cf3839d27964]
+Comment: Patch is refreshed based on 255.22 codebase
+
+Signed-off-by: Akash Hadke <Akash.Hadke@bmwtechworks.in>
+---
+ src/network/networkd-nexthop.c | 22 +++++++++++++++++-----
+ 1 file changed, 17 insertions(+), 5 deletions(-)
+
+diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c
+index 23941527cfdcf..81eb85b4a06c1 100644
+--- a/src/network/networkd-nexthop.c
++++ b/src/network/networkd-nexthop.c
+@@ -520,17 +520,29 @@ static bool nexthop_is_ready_to_configur
+         if (!link_is_ready_to_configure(link, false))
+                 return false;
+ 
++        /* Currently, we support the following three types of nexthops:
++         * 1. Simple nexthop - bound to the link, requires the underlying link is up.
++         * 2. Blackhole nexthop - not bound to the link.
++         * 3. Group nexthop - not bound to the link, but all group members must be configured first.
++         *
++         * Note, the kernel also supports fdb nexthop, but currently we do not support it. Note, fdb nexthop
++         * does not require IFF_UP. See rtm_to_nh_config() in net/ipv4/nexthop.c of kernel. */
++
++        /* Simple nexthop */
+         if (nexthop_owned_by_link(nexthop)) {
+-                /* TODO: fdb nexthop does not require IFF_UP. The conditions below needs to be updated
+-                 * when fdb nexthop support is added. See rtm_to_nh_config() in net/ipv4/nexthop.c of
+-                 * kernel. */
+                 if (link->set_flags_messages > 0)
+                         return false;
+                 if (!link_is_up(link))
+                         return false;
++
++                return gateway_is_ready(link, FLAGS_SET(nexthop->flags, RTNH_F_ONLINK), nexthop->family, &nexthop->gw);
+         }
+ 
+-        /* All group members must be configured first. */
++        /* Blackhole nexthop */
++        if (nexthop->blackhole)
++                return true;
++
++        /* Group nexthop */
+         HASHMAP_FOREACH(nhg, nexthop->group) {
+                 NextHop *g;
+ 
+@@ -552,7 +564,7 @@ static bool nexthop_is_ready_to_configur
+                 }
+         }
+ 
+-        return gateway_is_ready(link, FLAGS_SET(nexthop->flags, RTNH_F_ONLINK), nexthop->family, &nexthop->gw);
++        return true;
+ }
+ 
+ static int nexthop_process_request(Request *req, Link *link, NextHop *nexthop) {
diff --git a/meta/recipes-core/systemd/systemd/0025-network-route-bound-to-a-link-requires-the-link-is-up.patch b/meta/recipes-core/systemd/systemd/0025-network-route-bound-to-a-link-requires-the-link-is-up.patch
new file mode 100644
index 0000000000..cf4f786645
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/0025-network-route-bound-to-a-link-requires-the-link-is-up.patch
@@ -0,0 +1,57 @@
+From ed54648b47779d2ba24ceffd16b7b63ec0845043 Mon Sep 17 00:00:00 2001
+From: Yu Watanabe <watanabe.yu+github@gmail.com>
+Date: Mon, 9 Mar 2026 11:46:01 +0900
+Subject: [PATCH] network: route bound to a link requires the link is up
+
+We checked if the link is up only when configuring (explicit) nexthop,
+but we did not checked that when configuring route which has (implicit)
+nexthop.
+
+Let's move the checks from nexthop_is_ready_to_configure() to
+gateway_is_ready(), which is called for both implicit and explict
+nexthops.
+
+Fixes #40106.
+
+Upstream-Status: Backport [https://github.com/systemd/systemd/commit/ed54648b47779d2ba24ceffd16b7b63ec0845043]
+Comment: Patch is refreshed based on 255.22 codebase
+
+Signed-off-by: Akash Hadke <Akash.Hadke@bmwtechworks.in>
+---
+ src/network/networkd-nexthop.c    | 5 -----
+ src/network/networkd-route-util.c | 6 ++++++
+ 2 files changed, 6 insertions(+), 5 deletions(-)
+
+diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c
+index 81eb85b4a06c1..0e453e159748a 100644
+--- a/src/network/networkd-nexthop.c
++++ b/src/network/networkd-nexthop.c
+@@ -530,11 +530,6 @@ static bool nexthop_is_ready_to_configur
+ 
+         /* Simple nexthop */
+         if (nexthop_owned_by_link(nexthop)) {
+-                if (link->set_flags_messages > 0)
+-                        return false;
+-                if (!link_is_up(link))
+-                        return false;
+-
+                 return gateway_is_ready(link, FLAGS_SET(nexthop->flags, RTNH_F_ONLINK), nexthop->family, &nexthop->gw);
+         }
+ 
+diff --git a/src/network/networkd-route-util.c b/src/network/networkd-route-util.c
+index 37ace2d335c60..3a7156fc4129f 100644
+--- a/src/network/networkd-route-util.c
++++ b/src/network/networkd-route-util.c
+@@ -128,6 +128,12 @@ bool gateway_is_ready(Link *link, bool o
+         assert(link);
+         assert(link->manager);
+ 
++       if (link->set_flags_messages > 0)
++                return false;
++
++        if (!link_is_up(link))
++                return false;
++
+         if (onlink)
+                 return true;
+ 
diff --git a/meta/recipes-core/systemd/systemd/0026-test-network-add-test-case-for-issue-40106.patch b/meta/recipes-core/systemd/systemd/0026-test-network-add-test-case-for-issue-40106.patch
new file mode 100644
index 0000000000..1bef2cbbed
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/0026-test-network-add-test-case-for-issue-40106.patch
@@ -0,0 +1,117 @@
+From f47d180b855dd4eb0a11d44d001c99f475dda56b Mon Sep 17 00:00:00 2001
+From: Yu Watanabe <watanabe.yu+github@gmail.com>
+Date: Mon, 9 Mar 2026 12:31:41 +0900
+Subject: [PATCH] test-network: add test case for issue #40106
+
+Upstream-Status: Backport [https://github.com/systemd/systemd/commit/f47d180b855dd4eb0a11d44d001c99f475dda56b]
+
+Signed-off-by: Akash Hadke <Akash.Hadke@bmwtechworks.in>
+---
+ .../25-route-static-issue-40106-dummy.network |  7 ++++
+ .../25-route-static-issue-40106-vlan.netdev   |  7 ++++
+ .../25-route-static-issue-40106-vlan.network  | 15 +++++++
+ test/test-network/systemd-networkd-tests.py   | 41 +++++++++++++++++++
+ 4 files changed, 70 insertions(+)
+ create mode 100644 test/test-network/conf/25-route-static-issue-40106-dummy.network
+ create mode 100644 test/test-network/conf/25-route-static-issue-40106-vlan.netdev
+ create mode 100644 test/test-network/conf/25-route-static-issue-40106-vlan.network
+
+diff --git a/test/test-network/conf/25-route-static-issue-40106-dummy.network b/test/test-network/conf/25-route-static-issue-40106-dummy.network
+new file mode 100644
+index 0000000000000..08553b97f5f19
+--- /dev/null
++++ b/test/test-network/conf/25-route-static-issue-40106-dummy.network
+@@ -0,0 +1,7 @@
++# SPDX-License-Identifier: LGPL-2.1-or-later
++[Match]
++Name=dummy99
++
++[Network]
++ConfigureWithoutCarrier=true
++VLAN=vlan99
+diff --git a/test/test-network/conf/25-route-static-issue-40106-vlan.netdev b/test/test-network/conf/25-route-static-issue-40106-vlan.netdev
+new file mode 100644
+index 0000000000000..c40e7b755aefa
+--- /dev/null
++++ b/test/test-network/conf/25-route-static-issue-40106-vlan.netdev
+@@ -0,0 +1,7 @@
++# SPDX-License-Identifier: LGPL-2.1-or-later
++[NetDev]
++Kind=vlan
++Name=vlan99
++
++[VLAN]
++Id=99
+diff --git a/test/test-network/conf/25-route-static-issue-40106-vlan.network b/test/test-network/conf/25-route-static-issue-40106-vlan.network
+new file mode 100644
+index 0000000000000..442cf480fbc51
+--- /dev/null
++++ b/test/test-network/conf/25-route-static-issue-40106-vlan.network
+@@ -0,0 +1,15 @@
++# SPDX-License-Identifier: LGPL-2.1-or-later
++[Match]
++Name=vlan99
++
++[Network]
++ConfigureWithoutCarrier=true
++BindCarrier=dummy99
++
++[Address]
++Address=192.0.2.1/24
++
++[Route]
++Destination=198.51.100.1
++Type=multicast
++Scope=link
+diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
+index bbbab8e3fe636..6f2fe1b9eb65c 100755
+--- a/test/test-network/systemd-networkd-tests.py
++++ b/test/test-network/systemd-networkd-tests.py
+@@ -4603,6 +4603,47 @@ def test_route_via_ipv6(self):
+         self.assertRegex(output, '149.10.124.48/28 proto kernel scope link src 149.10.124.58')
+         self.assertRegex(output, '149.10.124.66 via inet6 2001:1234:5:8fff:ff:ff:ff:ff proto static')
+ 
++    def test_route_static_issue_40106(self):
++        check_output('ip link add dummy99 type dummy')
++        check_output('ip link set dummy99 up carrier off')
++        copy_network_unit(
++            '25-route-static-issue-40106-dummy.network',
++            '25-route-static-issue-40106-vlan.netdev',
++            '25-route-static-issue-40106-vlan.network',
++        )
++        start_networkd()
++        self.wait_online('dummy99:no-carrier')
++        self.wait_operstate('vlan99', operstate='off', setup_state='configuring')
++
++        # address can be configured even when the interface is down.
++        self.wait_address('vlan99', '192.0.2.1/24', ipv='-4', timeout_sec=10)
++        print('### ip -4 address show dev vlan99')
++        output = check_output('ip -4 address show dev vlan99')
++        print(output)
++        self.assertIn('inet 192.0.2.1/24 brd 192.0.2.255 scope global vlan99', output)
++
++        # route cannot be configured when the interface is down.
++        print('### ip -4 route show dev vlan99')
++        output = check_output('ip -4 route show dev vlan99')
++        print(output)
++        self.assertEqual(output, '')
++
++        # When cable is connected, the vlan becomes up by BindCarrier=, then
++        # the pending route is also configured.
++        check_output('ip link set dummy99 carrier on')
++        self.wait_online('dummy99:degraded', 'vlan99:routable')
++
++        print('### ip -4 address show dev vlan99')
++        output = check_output('ip -4 address show dev vlan99')
++        print(output)
++        self.assertIn('inet 192.0.2.1/24 brd 192.0.2.255 scope global vlan99', output)
++
++        print('### ip -4 route show dev vlan99')
++        output = check_output('ip -4 route show dev vlan99')
++        print(output)
++        self.assertIn('192.0.2.0/24 proto kernel scope link src 192.0.2.1', output)
++        self.assertIn('multicast 198.51.100.1 proto static scope link', output)
++
+     @expectedFailureIfModuleIsNotAvailable('tcp_dctcp')
+     def test_route_congctl(self):
+         copy_network_unit('25-route-congctl.network', '12-dummy.netdev')
diff --git a/meta/recipes-core/systemd/systemd_255.22.bb b/meta/recipes-core/systemd/systemd_255.22.bb
index e5a0fd9170..f5e0618def 100644
--- a/meta/recipes-core/systemd/systemd_255.22.bb
+++ b/meta/recipes-core/systemd/systemd_255.22.bb
@@ -37,6 +37,10 @@ SRC_URI += " \
            file://CVE-2026-29111-02.patch \
            file://CVE-2026-29111-03.patch \
            file://CVE-2026-29111-04.patch \
+           file://0023-network-introduce-link_is_up-helper-function.patch \
+           file://0024-network-check-if-gateway-is-ready-only-when-the-nexthop-is-bound-to-link.patch \
+           file://0025-network-route-bound-to-a-link-requires-the-link-is-up.patch \
+           file://0026-test-network-add-test-case-for-issue-40106.patch \
            "
 
 # patches needed by musl
