diff --git a/roles/kubernetes/control-plane/tasks/kubeadm-secondary.yml b/roles/kubernetes/control-plane/tasks/kubeadm-secondary.yml
index 99c3d66a20b6aedeaa9b3d9e6592b4e6c63e76a9..a4869fec8bfd0d2ea6e2b2a74334a30eee2934f9 100644
--- a/roles/kubernetes/control-plane/tasks/kubeadm-secondary.yml
+++ b/roles/kubernetes/control-plane/tasks/kubeadm-secondary.yml
@@ -19,6 +19,7 @@
   register: kubeadm_upload_cert
   when:
     - inventory_hostname == first_kube_control_plane
+    - not kube_external_ca_mode
 
 - name: Parse certificate key if not set
   set_fact:
@@ -49,11 +50,20 @@
   debug:
     msg: "{{ kubeadm_already_run.stat.exists }}"
 
-- name: Joining control plane node to the cluster.
+- name: Reset cert directory
   shell: >-
     if [ -f /etc/kubernetes/manifests/kube-apiserver.yaml ]; then
     {{ bin_dir }}/kubeadm reset -f --cert-dir {{ kube_cert_dir }};
-    fi &&
+    fi
+  environment:
+    PATH: "{{ bin_dir }}:{{ ansible_env.PATH }}"
+  when:
+    - inventory_hostname != first_kube_control_plane
+    - kubeadm_already_run is not defined or not kubeadm_already_run.stat.exists
+    - not kube_external_ca_mode
+
+- name: Joining control plane node to the cluster.
+  command: >-
     {{ bin_dir }}/kubeadm join
     --config {{ kube_config_dir }}/kubeadm-controlplane.yaml
     --ignore-preflight-errors=all
diff --git a/roles/kubernetes/control-plane/tasks/kubeadm-setup.yml b/roles/kubernetes/control-plane/tasks/kubeadm-setup.yml
index e99f2f8405e8763683652bfb2aa0ed407be9fa1c..23f798d649c999472ed7c8dddec04e69b2bb32bb 100644
--- a/roles/kubernetes/control-plane/tasks/kubeadm-setup.yml
+++ b/roles/kubernetes/control-plane/tasks/kubeadm-setup.yml
@@ -101,6 +101,7 @@
   changed_when: "'NEED-RENEW' in apiserver_sans_check.stdout"
   when:
     - kubeadm_already_run.stat.exists
+    - not kube_external_ca_mode
 
 - name: kubeadm | regenerate apiserver cert 1/2
   file:
@@ -112,6 +113,7 @@
   when:
     - kubeadm_already_run.stat.exists
     - apiserver_sans_check.changed
+    - not kube_external_ca_mode
 
 - name: kubeadm | regenerate apiserver cert 2/2
   command: >-
@@ -121,6 +123,7 @@
   when:
     - kubeadm_already_run.stat.exists
     - apiserver_sans_check.changed
+    - not kube_external_ca_mode
 
 - name: kubeadm | Initialize first master
   command: >-
@@ -129,7 +132,7 @@
     --config={{ kube_config_dir }}/kubeadm-config.yaml
     --ignore-preflight-errors=all
     --skip-phases={{ kubeadm_init_phases_skip | join(',') }}
-    --upload-certs
+    {{ kube_external_ca_mode | ternary('', '--upload-certs') }}
   register: kubeadm_init
   # Retry is because upload config sometimes fails
   retries: 3
diff --git a/roles/kubernetes/preinstall/tasks/0020-verify-settings.yml b/roles/kubernetes/preinstall/tasks/0020-verify-settings.yml
index 27c0bfdd5f2e6b47e46600c453d5724882cd51d3..08f4eaeb1a8e4f2d713c58976744327aa2c3f013 100644
--- a/roles/kubernetes/preinstall/tasks/0020-verify-settings.yml
+++ b/roles/kubernetes/preinstall/tasks/0020-verify-settings.yml
@@ -376,3 +376,11 @@
   when:
     - containerd_config is defined
     - not ignore_assert_errors
+
+- name: Stop if auto_renew_certificates is enabled when certificates are managed externally (kube_external_ca_mode is true)
+  assert:
+    that: not auto_renew_certificates
+    msg: "Variable auto_renew_certificates must be disabled when CA are managed externally:  kube_external_ca_mode = true"
+  when:
+    - kube_external_ca_mode
+    - not ignore_assert_errors
diff --git a/roles/kubespray-defaults/defaults/main.yaml b/roles/kubespray-defaults/defaults/main.yaml
index 351b4d60e1af643aeb0ded270ca103d30f227022..e1a35190186b9bd07ea891e3483f5d39017806bf 100644
--- a/roles/kubespray-defaults/defaults/main.yaml
+++ b/roles/kubespray-defaults/defaults/main.yaml
@@ -157,6 +157,12 @@ kube_token_dir: "{{ kube_config_dir }}/tokens"
 # cert files to. Not really changeable...
 kube_cert_group: kube-cert
 
+# Set to true when the CAs are managed externally.
+# When true, disables all tasks manipulating certificates. Ensure before the kubespray run that:
+# - Certificates and CAs are present in kube_cert_dir
+# - Kubeconfig files are present in kube_config_dir
+kube_external_ca_mode: false
+
 # Cluster Loglevel configuration
 kube_log_level: 2