diff --git a/gso/utils/workflow_steps.py b/gso/utils/workflow_steps.py
index 6ab00644bef668cecdf1c159e9502ec4879ff8a7..939feff6d433547b9cafbc7390f5c20100d80199 100644
--- a/gso/utils/workflow_steps.py
+++ b/gso/utils/workflow_steps.py
@@ -6,6 +6,7 @@ from typing import Any
 from orchestrator import inputstep, step
 from orchestrator.config.assignee import Assignee
 from orchestrator.types import State, UUIDstr
+from orchestrator.utils.errors import ProcessFailureError
 from orchestrator.utils.json import json_dumps
 from pydantic import ConfigDict
 from pydantic_forms.core import FormPage
@@ -121,3 +122,15 @@ def prompt_sharepoint_checklist_url(checklist_url: str) -> FormGenerator:
     yield SharepointPrompt
 
     return {}
+
+
+@step("Detect configuration drift")
+def detect_configuration_drift(callback_result: dict) -> None:
+    """Inspect the diff for deploying configuration, to make sure there is no drift."""
+    if callback_result["return_code"] != 0:
+        # If deployment never was successful in the first place, raise an exception.
+        raise ProcessFailureError(message="Provisioning proxy failure", details=callback_result)
+
+    output_lines = callback_result["output"]
+    if any(str.startswith(line, "-") or str.startswith(line, "+") for line in callback_result["output"]):
+        raise ProcessFailureError(message="Configuration drift detected", details=output_lines)
diff --git a/gso/workflows/router/validate_router.py b/gso/workflows/router/validate_router.py
index ab12ffd831cc9fe0717f24963578e1222bf66c59..5e1447cebcccca74250740b4774cdf02d41098f6 100644
--- a/gso/workflows/router/validate_router.py
+++ b/gso/workflows/router/validate_router.py
@@ -1,17 +1,18 @@
 """Router validation workflow. Used in a nightly schedule."""
 
-import json
-
 from orchestrator.targets import Target
 from orchestrator.utils.errors import ProcessFailureError
-from orchestrator.utils.json import json_dumps
 from orchestrator.workflow import StepList, done, init, step, workflow
 from orchestrator.workflows.steps import resync, store_process_subscription, unsync
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from workflows.router.update_ibgp_mesh import add_p_to_mesh_dry
 
 from gso.products.product_types.router import Router
 from gso.services import infoblox
-from gso.services.lso_client import execute_playbook, lso_interaction
+from gso.services.librenms_client import LibreNMSClient
+from gso.services.lso_client import anonymous_lso_interaction
+from gso.services.netbox_client import NetboxClient
+from gso.utils.workflow_steps import deploy_base_config_dry, detect_configuration_drift
 
 
 @step("Verify IPAM resources for loopback interface")
@@ -26,17 +27,26 @@ def verify_ipam_loopback(subscription: Router) -> None:
         raise ProcessFailureError(msg)
 
 
-@step("Validate router configuration")
-def validate_router_config(subscription: Router, callback_route: str) -> None:
-    """Run an Ansible playbook that validates the configuration that is present on an active Router."""
-    extra_vars = {"wfo_router": json.loads(json_dumps(subscription)), "verb": "validate"}
+@step("Verify correct Netbox entry")
+def verify_netbox_entry(subscription: Router) -> None:
+    """Validate the Netbox entry for a Router.
 
-    execute_playbook(
-        playbook_name="base_config.yaml",
-        callback_route=callback_route,
-        inventory=subscription.router.router_fqdn,
-        extra_vars=extra_vars,
-    )
+    This will only ensure existence of the node itself in Netbox. Validation of separate interfaces takes places in
+    other subscriptions' validation workflows.
+    """
+    client = NetboxClient()
+    #  Try and fetch the host, which will raise an exception on failure.
+    client.get_device_by_name(subscription.router.router_fqdn)
+
+
+@step("Verify correct LibreNMS entry")
+def verify_librenms_entry(subscription: Router) -> None:
+    """Validate the LibreNMS entry for a Router.
+
+    Raises an HTTP error 404 when the device is not present in LibreNMS.
+    """
+    client = LibreNMSClient()
+    client.get_device(subscription.router.router_fqdn)
 
 
 @workflow(
@@ -48,14 +58,20 @@ def validate_router() -> StepList:
     """Validate an existing, active Router subscription.
 
     * Verify that the loopback interface is correctly configured in :term:`IPAM`.
+    * Verify that the router is correctly configured in Netbox.
+    * Verify that the router is correctly configured in LibreNMS.
     * Redeploy base config to verify the configuration is intact.
+    * Validate configuration of the iBGP mesh
     """
     return (
         init
         >> store_process_subscription(Target.SYSTEM)
         >> unsync
         >> verify_ipam_loopback
-        >> lso_interaction(validate_router_config)
+        >> verify_netbox_entry
+        >> verify_librenms_entry
+        >> anonymous_lso_interaction(deploy_base_config_dry, detect_configuration_drift)
+        >> anonymous_lso_interaction(add_p_to_mesh_dry, detect_configuration_drift)
         >> resync
         >> done
     )