diff --git a/roles/kubernetes/control-plane/tasks/kubeadm-secondary.yml b/roles/kubernetes/control-plane/tasks/kubeadm-secondary.yml
index 1af7f0c6e7261fb551c8f49495810db8ef904842..3a4ee8aa62558698db0292a74d310c1d67127950 100644
--- a/roles/kubernetes/control-plane/tasks/kubeadm-secondary.yml
+++ b/roles/kubernetes/control-plane/tasks/kubeadm-secondary.yml
@@ -57,6 +57,7 @@
     {{ bin_dir }}/kubeadm join
     --config {{ kube_config_dir }}/kubeadm-controlplane.yaml
     --ignore-preflight-errors=all
+    --skip-phases={{ kubeadm_join_phases_skip | join(',') }}
   environment:
     PATH: "{{ bin_dir }}:{{ ansible_env.PATH }}"
   register: kubeadm_join_control_plane
diff --git a/roles/kubernetes/kubeadm/tasks/main.yml b/roles/kubernetes/kubeadm/tasks/main.yml
index 6a02f0dab7a3ea737a1532280a45b7ed35a15020..420ea0bb68163216f202b5ac3deb458d7fe194c4 100644
--- a/roles/kubernetes/kubeadm/tasks/main.yml
+++ b/roles/kubernetes/kubeadm/tasks/main.yml
@@ -76,6 +76,7 @@
         {{ bin_dir }}/kubeadm join
         --config {{ kube_config_dir }}/kubeadm-client.conf
         --ignore-preflight-errors=DirAvailable--etc-kubernetes-manifests
+        --skip-phases={{ kubeadm_join_phases_skip | join(',') }}
       register: kubeadm_join
 
   rescue:
@@ -86,6 +87,7 @@
         {{ bin_dir }}/kubeadm join
         --config {{ kube_config_dir }}/kubeadm-client.conf
         --ignore-preflight-errors=all
+        --skip-phases={{ kubeadm_join_phases_skip | join(',') }}
       register: kubeadm_join
 
   always:
diff --git a/roles/kubespray-defaults/defaults/main.yaml b/roles/kubespray-defaults/defaults/main.yaml
index dade9d893f9baabd77007c59392dd17b550ddea5..bf73304418cd4b2165f0ec7e51b28c7011a9fae4 100644
--- a/roles/kubespray-defaults/defaults/main.yaml
+++ b/roles/kubespray-defaults/defaults/main.yaml
@@ -38,6 +38,12 @@ kubeadm_init_phases_skip: >-
   {{ kubeadm_init_phases_skip_default }}
   {%- endif -%}
 
+# List of kubeadm phases that should be skipped when joining a new node
+# You may need to set this to ['preflight'] for air-gaped deployments to avoid failing connectivity tests.
+kubeadm_join_phases_skip_default: []
+kubeadm_join_phases_skip: >-
+ {{ kubeadm_join_phases_skip_default }}
+
 # A string slice of values which specify the addresses to use for NodePorts.
 # Values may be valid IP blocks (e.g. 1.2.3.0/24, 1.2.3.4/32).
 # The default empty string slice ([]) means to use all local addresses.