diff --git a/gso/migrations/versions/2025-06-24_285954f5ec04_add_l3_service_redeploy_workflow.py b/gso/migrations/versions/2025-06-24_285954f5ec04_add_l3_service_redeploy_workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..05d80fc80df115f34a91b7dec88c4b0ad10737f0
--- /dev/null
+++ b/gso/migrations/versions/2025-06-24_285954f5ec04_add_l3_service_redeploy_workflow.py
@@ -0,0 +1,42 @@
+"""Add L3 service redeploy workflow.
+
+Revision ID: 285954f5ec04
+Revises: b2b5137ef0c7
+Create Date: 2025-06-24 16:49:06.495691
+
+"""
+from alembic import op
+from orchestrator.migrations.helpers import (
+    add_products_to_workflow_by_product_tag,
+    create_workflow,
+    delete_workflow,
+    remove_products_from_workflow_by_product_tag
+)
+
+# revision identifiers, used by Alembic.
+revision = '285954f5ec04'
+down_revision = 'b2b5137ef0c7'
+branch_labels = None
+depends_on = None
+
+new_workflow = {
+    "name": "redeploy_l3_core_service",
+    "target": "MODIFY",
+    "description": "Redeploy Layer 3 service",
+    "product_type": "GeantIP"
+}
+additional_product_tags = ["IAS", "LHC", "COP", "RE_LHCONE", "RE_PEER"]
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    create_workflow(conn, new_workflow)
+    for product in additional_product_tags:
+        add_products_to_workflow_by_product_tag(conn, new_workflow["name"], product)
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    for product in additional_product_tags:
+        remove_products_from_workflow_by_product_tag(conn, new_workflow["name"], product)
+    delete_workflow(conn, new_workflow["name"])
diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json
index 54bcbc3236f11348a3533cc69ba9261711abf660..d2fa50f4813747a140061cda8f108434fe7c8ea4 100644
--- a/gso/translations/en-GB.json
+++ b/gso/translations/en-GB.json
@@ -159,6 +159,7 @@
         "modify_r_and_e_lhcone": "Modify R&E LHCONE",
         "promote_p_to_pe": "Promote P to PE",
         "redeploy_base_config": "Redeploy base config",
+        "redeploy_l3_core_service": "Redeploy Layer 3 service",
         "redeploy_vrf": "Redeploy VRF router list",
         "task_check_site_connectivity": "Check NETCONF connectivity of a Site",
         "task_clean_old_tasks": "Remove old cleanup tasks",
diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index 827d2a07c5db07c4e000c94b27fb982a496a90f2..d37cd65309eb7fdd4da0edfe5c8af299c77a7890 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -120,6 +120,9 @@ LazyWorkflowInstance("gso.workflows.edge_port.create_imported_edge_port", "creat
 LazyWorkflowInstance("gso.workflows.edge_port.import_edge_port", "import_edge_port")
 LazyWorkflowInstance("gso.workflows.edge_port.migrate_edge_port", "migrate_edge_port")
 
+# All L3 core services
+LazyWorkflowInstance("gso.workflows.l3_core_service.redeploy_l3_core_service", "redeploy_l3_core_service")
+
 #  IAS workflows
 LazyWorkflowInstance("gso.workflows.l3_core_service.ias.create_ias", "create_ias")
 LazyWorkflowInstance("gso.workflows.l3_core_service.ias.modify_ias", "modify_ias")
diff --git a/gso/workflows/l3_core_service/redeploy_l3_core_service.py b/gso/workflows/l3_core_service/redeploy_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..cfaaacc7f75dff4462488fd6519b30adf07b9054
--- /dev/null
+++ b/gso/workflows/l3_core_service/redeploy_l3_core_service.py
@@ -0,0 +1,87 @@
+"""Base functionality for modifying an L3 Core Service subscription."""
+
+from typing import TypeAlias, cast
+
+from orchestrator import workflow
+from orchestrator.domain import SubscriptionModel
+from orchestrator.forms import FormPage
+from orchestrator.targets import Target
+from orchestrator.workflow import StepList, begin, done
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from pydantic import ConfigDict
+from pydantic_forms.types import FormGenerator, UUIDstr
+from pydantic_forms.validators import Choice
+
+from gso.products.product_blocks.l3_core_service import AccessPort
+from gso.products.product_types.edge_port import EdgePort
+from gso.services.lso_client import lso_interaction
+from gso.services.partners import get_partner_by_id
+from gso.utils.types.tt_number import TTNumber
+from gso.workflows.l3_core_service.base_create_l3_core_service import (
+    deploy_bgp_peers_dry,
+    deploy_bgp_peers_real,
+    provision_sbp_dry,
+    provision_sbp_real,
+)
+
+
+def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
+    """Get input which Access Port should be re-deployed."""
+    subscription = SubscriptionModel.from_subscription(subscription_id)
+    product_name = subscription.product.name
+
+    def access_port_selector() -> TypeAlias:
+        """Generate a dropdown selector for choosing an Access Port in an input form."""
+        access_ports = subscription.l3_core.ap_list  # type: ignore[attr-defined]
+        options = {
+            str(access_port.subscription_instance_id): (
+                f"{access_port.sbp.gs_id} on "
+                f"{EdgePort.from_subscription(access_port.sbp.edge_port.owner_subscription_id).description} "
+                f"({access_port.ap_type})"
+            )
+            for access_port in access_ports
+        }
+
+        return cast(
+            type[Choice],
+            Choice.__call__(
+                "Select an Access Port",
+                zip(options.keys(), options.items(), strict=True),
+            ),
+        )
+
+    class AccessPortSelectionForm(FormPage):
+        model_config = ConfigDict(title=f"Re-deploy {product_name} subscription")
+
+        tt_number: TTNumber
+        access_port: access_port_selector()  # type: ignore[valid-type]
+
+    user_input = yield AccessPortSelectionForm
+    partner_name = get_partner_by_id(subscription.customer_id).name
+    access_port = AccessPort.from_db(user_input.access_port)
+    access_port_fqdn = EdgePort.from_subscription(
+        access_port.sbp.edge_port.owner_subscription_id
+    ).edge_port.node.router_fqdn
+
+    return user_input.model_dump() | {"edge_port_fqdn_list": [access_port_fqdn], "partner_name": partner_name}
+
+
+@workflow(
+    "Redeploy Layer 3 service",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
+    target=Target.MODIFY,
+)
+def redeploy_l3_core_service() -> StepList:
+    """Redeploy a Layer 3 subscription."""
+    return (
+        begin
+        >> store_process_subscription(Target.MODIFY)
+        >> unsync
+        >> lso_interaction(provision_sbp_dry)
+        >> lso_interaction(provision_sbp_real)
+        >> lso_interaction(deploy_bgp_peers_dry)
+        >> lso_interaction(deploy_bgp_peers_real)
+        >> resync
+        >> done
+    )
diff --git a/test/workflows/l3_core_service/test_redeploy_l3_core_service.py b/test/workflows/l3_core_service/test_redeploy_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..46b7c52b53b6568379aeb8d9737ba25689a84dbc
--- /dev/null
+++ b/test/workflows/l3_core_service/test_redeploy_l3_core_service.py
@@ -0,0 +1,88 @@
+from copy import deepcopy
+
+import pytest
+from orchestrator.domain import SubscriptionModel
+
+from gso.workflows.l3_core_service.shared import L3_PRODUCT_NAMES
+from test.workflows import assert_complete, assert_lso_interaction_success, extract_state, run_workflow
+
+
+@pytest.mark.parametrize("product_name", L3_PRODUCT_NAMES)
+@pytest.mark.workflow()
+def test_redeploy_l3_core_service_success(faker, l3_core_service_subscription_factory, product_name):
+    subscription = l3_core_service_subscription_factory(product_name=product_name)
+    old_subscription: SubscriptionModel = deepcopy(subscription)
+    access_port = subscription.l3_core.ap_list[0]
+    input_form_data = [
+        {"subscription_id": str(subscription.subscription_id)},
+        {"tt_number": faker.tt_number(), "access_port": str(access_port.subscription_instance_id)},
+    ]
+
+    result, process_stat, step_log = run_workflow("redeploy_l3_core_service", input_form_data)
+
+    for _ in range(4):
+        result, step_log = assert_lso_interaction_success(result, process_stat, step_log)
+
+    assert_complete(result)
+    state = extract_state(result)
+    subscription = SubscriptionModel.from_subscription(state["subscription_id"])
+    ap_list = subscription.l3_core.ap_list
+    old_ap_list = old_subscription.l3_core.ap_list
+
+    # Assertions that ensure the subscription is unchanged
+    for old_access_port, access_port in zip(old_ap_list, ap_list, strict=False):
+        assert access_port.sbp.gs_id == old_access_port.sbp.gs_id
+        assert access_port.sbp.is_tagged == old_access_port.sbp.is_tagged
+        assert access_port.sbp.vlan_id == old_access_port.sbp.vlan_id
+        assert str(access_port.sbp.ipv4_address) == str(old_access_port.sbp.ipv4_address)
+        assert access_port.sbp.ipv4_mask == old_access_port.sbp.ipv4_mask
+        assert str(access_port.sbp.ipv6_address) == str(old_access_port.sbp.ipv6_address)
+        assert access_port.sbp.ipv6_mask == old_access_port.sbp.ipv6_mask
+        assert access_port.sbp.custom_firewall_filters == old_access_port.sbp.custom_firewall_filters
+
+        assert access_port.sbp.bgp_session_list[0].bfd_enabled == old_access_port.sbp.bgp_session_list[0].bfd_enabled
+        assert (
+            access_port.sbp.bgp_session_list[0].has_custom_policies
+            == old_access_port.sbp.bgp_session_list[0].has_custom_policies
+        )
+        assert (
+            access_port.sbp.bgp_session_list[0].authentication_key
+            == old_access_port.sbp.bgp_session_list[0].authentication_key
+        )
+        assert (
+            access_port.sbp.bgp_session_list[0].multipath_enabled
+            == old_access_port.sbp.bgp_session_list[0].multipath_enabled
+        )
+        assert (
+            access_port.sbp.bgp_session_list[0].send_default_route
+            == old_access_port.sbp.bgp_session_list[0].send_default_route
+        )
+        assert access_port.sbp.bgp_session_list[0].is_passive == old_access_port.sbp.bgp_session_list[0].is_passive
+
+        assert access_port.sbp.bgp_session_list[1].bfd_enabled == old_access_port.sbp.bgp_session_list[1].bfd_enabled
+        assert (
+            access_port.sbp.bgp_session_list[1].has_custom_policies
+            == old_access_port.sbp.bgp_session_list[1].has_custom_policies
+        )
+        assert (
+            access_port.sbp.bgp_session_list[1].authentication_key
+            == old_access_port.sbp.bgp_session_list[1].authentication_key
+        )
+        assert (
+            access_port.sbp.bgp_session_list[1].multipath_enabled
+            == old_access_port.sbp.bgp_session_list[1].multipath_enabled
+        )
+        assert (
+            access_port.sbp.bgp_session_list[1].send_default_route
+            == old_access_port.sbp.bgp_session_list[1].send_default_route
+        )
+        assert access_port.sbp.bgp_session_list[1].is_passive == old_access_port.sbp.bgp_session_list[1].is_passive
+
+        assert access_port.sbp.v4_bfd_settings.bfd_enabled == old_access_port.sbp.v4_bfd_settings.bfd_enabled
+        assert access_port.sbp.v4_bfd_settings.bfd_interval_rx == old_access_port.sbp.v4_bfd_settings.bfd_interval_rx
+        assert access_port.sbp.v4_bfd_settings.bfd_interval_tx == old_access_port.sbp.v4_bfd_settings.bfd_interval_tx
+        assert access_port.sbp.v4_bfd_settings.bfd_multiplier == old_access_port.sbp.v4_bfd_settings.bfd_multiplier
+        assert access_port.sbp.v6_bfd_settings.bfd_enabled == old_access_port.sbp.v6_bfd_settings.bfd_enabled
+        assert access_port.sbp.v6_bfd_settings.bfd_interval_rx == old_access_port.sbp.v6_bfd_settings.bfd_interval_rx
+        assert access_port.sbp.v6_bfd_settings.bfd_interval_tx == old_access_port.sbp.v6_bfd_settings.bfd_interval_tx
+        assert access_port.sbp.v6_bfd_settings.bfd_multiplier == old_access_port.sbp.v6_bfd_settings.bfd_multiplier