diff --git a/cluster/cluster.go b/cluster/cluster.go
index 4f0d92ef..d92053a3 100644
--- a/cluster/cluster.go
+++ b/cluster/cluster.go
@@ -58,7 +58,7 @@ type Cluster struct {
 	InactiveHosts                    []*hosts.Host
 	K8sWrapTransport                 transport.WrapperFunc
 	KubeClient                       *kubernetes.Clientset
-	KubernetesServiceIP              net.IP
+	KubernetesServiceIP              []net.IP
 	LocalKubeConfigPath              string
 	LocalConnDialerFactory           hosts.DialerFactory
 	PrivateRegistriesMap             map[string]v3.PrivateRegistry
@@ -736,7 +736,7 @@ func InitClusterObject(ctx context.Context, rkeConfig *v3.RancherKubernetesEngin
 	}
 	// extract cluster network configuration
 	if err = c.setNetworkOptions(); err != nil {
-		return nil, fmt.Errorf("failed set network options: %v", err)
+		return nil, fmt.Errorf("Failed to set network options: %v", err)
 	}
 
 	// Register cloud provider
diff --git a/cluster/network.go b/cluster/network.go
index 2612e0c8..d9e42372 100644
--- a/cluster/network.go
+++ b/cluster/network.go
@@ -289,6 +289,7 @@ var EtcdClientPortList = []string{
 }
 
 var CalicoNetworkLabels = []string{CalicoNodeLabel, CalicoControllerLabel}
+var IPv6CompatibleNetworkPlugins = []string{CalicoNetworkPlugin}
 
 func (c *Cluster) deployNetworkPlugin(ctx context.Context, data map[string]interface{}) error {
 	log.Infof(ctx, "[network] Setting up network plugin: %s", c.Network.Plugin)
diff --git a/cluster/validation.go b/cluster/validation.go
index 6f07e97b..880e56d8 100644
--- a/cluster/validation.go
+++ b/cluster/validation.go
@@ -208,6 +208,31 @@ func validateNetworkOptions(c *Cluster) error {
 	if c.Network.Plugin == FlannelNetworkPlugin && c.Network.MTU != 0 {
 		return fmt.Errorf("Network plugin [%s] does not support configuring MTU", FlannelNetworkPlugin)
 	}
+	dualStack := false
+	serviceClusterRanges := strings.Split(c.Services.KubeAPI.ServiceClusterIPRange, ",")
+	if len(serviceClusterRanges) > 1 {
+		logrus.Debugf("Found more than 1 service cluster IP range, assuming dual stack")
+		dualStack = true
+	}
+	clusterCIDRs := strings.Split(c.Services.KubeController.ClusterCIDR, ",")
+	if len(clusterCIDRs) > 1 {
+		logrus.Debugf("Found more than 1 cluster CIDR, assuming dual stack")
+		dualStack = true
+	}
+	if dualStack {
+		IPv6CompatibleNetworkPluginFound := false
+		for _, networkPlugin := range IPv6CompatibleNetworkPlugins {
+			if c.Network.Plugin == networkPlugin {
+				logrus.Debugf("Found IPv6 compatible network plugin [%s] == [%s]", c.Network.Plugin, networkPlugin)
+				IPv6CompatibleNetworkPluginFound = true
+				break
+			}
+		}
+		if !IPv6CompatibleNetworkPluginFound {
+			return fmt.Errorf("Network plugin [%s] does not support IPv6 (dualstack)", c.Network.Plugin)
+		}
+	}
+
 	if c.Network.Plugin == AciNetworkPlugin {
 		//Skip cloud options and throw an error.
 		cloudOptionsList := []string{AciEpRegistry, AciOpflexMode, AciUseHostNetnsVolume, AciUseOpflexServerVolume,
diff --git a/pki/pki.go b/pki/pki.go
index d3d2f50f..c042dbd3 100644
--- a/pki/pki.go
+++ b/pki/pki.go
@@ -83,7 +83,7 @@ func RegenerateEtcdCertificate(
 	etcdHost *hosts.Host,
 	etcdHosts []*hosts.Host,
 	clusterDomain string,
-	KubernetesServiceIP net.IP) (map[string]CertificatePKI, error) {
+	KubernetesServiceIP []net.IP) (map[string]CertificatePKI, error) {
 
 	etcdName := GetCrtNameForHost(etcdHost, EtcdCertName)
 	log.Infof(ctx, "[certificates] Regenerating new %s certificate and key", etcdName)
diff --git a/pki/pki_test.go b/pki/pki_test.go
index d8637cc9..feabc0fc 100644
--- a/pki/pki_test.go
+++ b/pki/pki_test.go
@@ -86,8 +86,8 @@ func TestPKI(t *testing.T) {
 		net.ParseIP("127.0.0.1"),
 		net.ParseIP(rkeConfig.Nodes[0].InternalAddress),
 		net.ParseIP(rkeConfig.Nodes[0].Address),
-		kubernetesServiceIP,
 	}
+	kubeAPIAltIPs = append(kubeAPIAltIPs, kubernetesServiceIP...)
 
 	for _, testIP := range kubeAPIAltIPs {
 		found := false
diff --git a/pki/util.go b/pki/util.go
index 419be1c0..784cf325 100644
--- a/pki/util.go
+++ b/pki/util.go
@@ -163,7 +163,7 @@ func GetIPHostAltnamesForHost(host *hosts.Host) *cert.AltNames {
 	}
 }
 
-func GetAltNames(cpHosts []*hosts.Host, clusterDomain string, KubernetesServiceIP net.IP, SANs []string) *cert.AltNames {
+func GetAltNames(cpHosts []*hosts.Host, clusterDomain string, KubernetesServiceIP []net.IP, SANs []string) *cert.AltNames {
 	ips := []net.IP{}
 	dnsNames := []string{}
 	for _, host := range cpHosts {
@@ -198,7 +198,7 @@ func GetAltNames(cpHosts []*hosts.Host, clusterDomain string, KubernetesServiceI
 	}
 
 	ips = append(ips, net.ParseIP("127.0.0.1"))
-	ips = append(ips, KubernetesServiceIP)
+	ips = append(ips, KubernetesServiceIP...)
 	dnsNames = append(dnsNames, []string{
 		"localhost",
 		"kubernetes",
@@ -379,19 +379,24 @@ func getCertKeys(rkeNodes []v3.RKEConfigNode, nodeRole string, rkeConfig *v3.Ran
 	return certList
 }
 
-func GetKubernetesServiceIP(serviceClusterRange string) (net.IP, error) {
-	ip, ipnet, err := net.ParseCIDR(serviceClusterRange)
-	if err != nil {
-		return nil, fmt.Errorf("Failed to get kubernetes service IP from Kube API option [service_cluster_ip_range]: %v", err)
-	}
-	ip = ip.Mask(ipnet.Mask)
-	for j := len(ip) - 1; j >= 0; j-- {
-		ip[j]++
-		if ip[j] > 0 {
-			break
+func GetKubernetesServiceIP(serviceClusterRange string) ([]net.IP, error) {
+	var serviceIPs []net.IP
+	serviceClusterRanges := strings.Split(serviceClusterRange, ",")
+	for _, serviceClusterRange := range serviceClusterRanges {
+		ip, ipnet, err := net.ParseCIDR(serviceClusterRange)
+		if err != nil {
+			return nil, fmt.Errorf("Failed to get kubernetes service IP from Kube API option [service_cluster_ip_range]: %v", err)
 		}
+		ip = ip.Mask(ipnet.Mask)
+		for j := len(ip) - 1; j >= 0; j-- {
+			ip[j]++
+			if ip[j] > 0 {
+				break
+			}
+		}
+		serviceIPs = append(serviceIPs, ip)
 	}
-	return ip, nil
+	return serviceIPs, nil
 }
 
 func GetLocalKubeConfig(configPath, configDir string) string {