diff --git a/gso/services/provisioning_proxy.py b/gso/services/provisioning_proxy.py
index 862a750e82124e5836c23312e11294e90f58e56c..11068bac93dc6eb1492e545a9cdd5a674c926187 100644
--- a/gso/services/provisioning_proxy.py
+++ b/gso/services/provisioning_proxy.py
@@ -4,6 +4,7 @@ LSO is responsible for executing Ansible playbooks, that deploy subscriptions.
 """
 import json
 import logging
+from typing import NoReturn
 
 import requests
 from orchestrator import inputstep
@@ -20,6 +21,7 @@ from gso.products.product_types.device import DeviceProvisioning
 from gso.products.product_types.iptrunk import Iptrunk, IptrunkProvisioning
 
 logger = logging.getLogger(__name__)
+DEFAULT_LABEL = "Provisioning proxy is running. Please come back later for the results."
 
 
 class CUDOperation(strEnum):
@@ -140,7 +142,7 @@ def deprovision_ip_trunk(subscription: Iptrunk, process_id: UUIDstr, dry_run: bo
 
 
 @inputstep("Await provisioning proxy results", assignee=Assignee("SYSTEM"))
-def await_pp_results(subscription: SubscriptionModel, label_text: str) -> FormGenerator:
+def await_pp_results(subscription: SubscriptionModel, label_text: str = DEFAULT_LABEL) -> FormGenerator:
     class ProvisioningResultPage(FormPage):
         class Config:
             title = f"Deploying {subscription.product.name}..."
@@ -150,7 +152,7 @@ def await_pp_results(subscription: SubscriptionModel, label_text: str) -> FormGe
         confirm: Accept = Accept("INCOMPLETE")
 
         @validator("pp_run_results", allow_reuse=True, pre=True, always=True)
-        def run_results_must_be_given(cls, run_results: dict) -> dict | None:
+        def run_results_must_be_given(cls, run_results: dict) -> dict | NoReturn:
             if run_results is None:
                 raise ValueError("Run results may not be empty. Wait for the provisioning proxy to finish.")
             return run_results
@@ -172,8 +174,9 @@ def confirm_pp_results(state: State) -> FormGenerator:
 
         run_status: str = ReadOnlyField(state["pp_run_results"]["status"])
         run_results: LongText = ReadOnlyField(f"{state['pp_run_results']['output']}")
+        pp_result_good_enough: bool = state["pp_run_results"]["return_code"] == 0
         confirm: Accept = Accept("INCOMPLETE")
 
-    yield ConfirmRunPage
+    confirmation = yield ConfirmRunPage
 
-    return state
+    return {"pp_did_succeed": confirmation.pp_result_good_enough}
diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json
index 418e7e852d144fe0f98d0bd9417a7b90cef63bfe..ea950f27471b99b21b4a8b1121fc2ef9a1df85b8 100644
--- a/gso/translations/en-GB.json
+++ b/gso/translations/en-GB.json
@@ -5,6 +5,8 @@
             "confirm_info": "Please verify this form looks correct.",
 
             "pp_run_results": "Provisioning proxy results are not ready yet.",
+            "pp_result_good_enough": "Provisioning Proxy results look good (enough).",
+            "pp_result_good_enough_info": "If set to false, this will retry the provisioning up to two times.",
 
             "site_bgp_community_id": "Site BGP community ID",
             "site_internal_id": "Site internal ID",
diff --git a/gso/workflows/device/create_device.py b/gso/workflows/device/create_device.py
index f162f5d4cea9a25b4f77de4c5698b7d18566ee0e..cab0f3230a71243a7b642160018b85c2c317a090 100644
--- a/gso/workflows/device/create_device.py
+++ b/gso/workflows/device/create_device.py
@@ -9,7 +9,7 @@ from orchestrator.forms import FormPage
 from orchestrator.forms.validators import Choice
 from orchestrator.targets import Target
 from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr
-from orchestrator.workflow import StepList, done, init, step, workflow
+from orchestrator.workflow import StepList, conditional, done, init, step, workflow
 from orchestrator.workflows.steps import resync, set_status, store_process_subscription
 from orchestrator.workflows.utils import wrap_create_initial_input_form
 
@@ -118,7 +118,7 @@ def initialize_subscription(
 
     subscription = device.DeviceProvisioning.from_other_lifecycle(subscription, SubscriptionLifecycle.PROVISIONING)
 
-    return {"subscription": subscription}
+    return {"subscription": subscription, "pp_did_succeed": False}
 
 
 @step("Provision device [DRY RUN]")
@@ -153,24 +153,50 @@ def provision_device_real(subscription: DeviceProvisioning, process_id: UUIDstr)
     }
 
 
+@step("Reset Provisioning Proxy state")
+def reset_pp_success_state() -> State:
+    return {"pp_did_succeed": False}
+
+
 @workflow(
     "Create device",
     initial_input_form=wrap_create_initial_input_form(initial_input_form_generator),
     target=Target.CREATE,
 )
 def create_device() -> StepList:
+    should_retry_pp_steps = conditional(lambda state: not state.get("pp_did_succeed"))
+
     return (
         init
         >> create_subscription
         >> store_process_subscription(Target.CREATE)
         >> initialize_subscription
         >> get_info_from_ipam
-        >> provision_device_dry
-        >> await_pp_results
-        >> confirm_pp_results
-        >> provision_device_real
-        >> await_pp_results
-        >> confirm_pp_results
+        # First attempt at dry run
+        >> should_retry_pp_steps(provision_device_dry)
+        >> should_retry_pp_steps(await_pp_results)
+        >> should_retry_pp_steps(confirm_pp_results)
+        # Second attempt
+        >> should_retry_pp_steps(provision_device_dry)
+        >> should_retry_pp_steps(await_pp_results)
+        >> should_retry_pp_steps(confirm_pp_results)
+        # Third and last try
+        >> should_retry_pp_steps(provision_device_dry)
+        >> should_retry_pp_steps(await_pp_results)
+        >> should_retry_pp_steps(confirm_pp_results)
+        >> reset_pp_success_state
+        # First attempt at provisioning
+        >> should_retry_pp_steps(provision_device_real)
+        >> should_retry_pp_steps(await_pp_results)
+        >> should_retry_pp_steps(confirm_pp_results)
+        # Second attempt
+        >> should_retry_pp_steps(provision_device_real)
+        >> should_retry_pp_steps(await_pp_results)
+        >> should_retry_pp_steps(confirm_pp_results)
+        # Third and last try
+        >> should_retry_pp_steps(provision_device_real)
+        >> should_retry_pp_steps(await_pp_results)
+        >> should_retry_pp_steps(confirm_pp_results)
         >> set_status(SubscriptionLifecycle.ACTIVE)
         >> resync
         >> done
diff --git a/tox.ini b/tox.ini
index 9ecc338588e067595315f54ff124d01965558ec6..8bfef47e58735f1d937e8cb31b6e01a9a81e4cb4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,14 +1,11 @@
 [flake8]
-ignore = D100,D101,D102,D103,D104,D105,D106,D107,D202,E501,RST301,RST304,W503,E203,C417,T202,S101
-; extend-ignore = E203
+; Allow >> on newline (W503), and allow cls as first argument for pydantic validators (B902)
+ignore = B902,W503
 exclude = .git,.*_cache,.eggs,*.egg-info,__pycache__,venv,.tox,gso/migrations,docs
 enable-extensions = G
 select = B,C,D,E,F,G,I,N,S,T,W,B902,B903,R
 max-line-length = 120
 ban-relative-imports = true
-per-file-ignores =
-	# Allow first argument to be cls instead of self for pydantic validators
-	gso/*: B902
 
 [testenv]
 deps =