diff --git a/NEWS b/NEWS
index c7440b476..e09f59d64 100644
--- a/NEWS
+++ b/NEWS
@@ -33,6 +33,9 @@ Post-v2.11.0
      * Added Policy-based routing(PBR) support to create permit/deny/reroute
        policies on the logical router. New table(Logical_Router_Policy) added in
        OVN-NB schema. New "ovn-nbctl" commands to add/delete/list PBR policies.
+     * Support for Transport Zones, a way to separate chassis into
+       logical groups which results in tunnels only been formed between
+       members of the same transport zone(s).
    - New QoS type "linux-netem" on Linux.
    - Added support for TLS Server Name Indication (SNI).
 
diff --git a/ovn/controller/chassis.c b/ovn/controller/chassis.c
index 58d5d49d5..0f537f1f7 100644
--- a/ovn/controller/chassis.c
+++ b/ovn/controller/chassis.c
@@ -19,6 +19,7 @@
 #include "chassis.h"
 
 #include "lib/smap.h"
+#include "lib/sset.h"
 #include "lib/vswitch-idl.h"
 #include "openvswitch/dynamic-string.h"
 #include "openvswitch/vlog.h"
@@ -73,13 +74,34 @@ get_cms_options(const struct smap *ext_ids)
     return smap_get_def(ext_ids, "ovn-cms-options", "");
 }
 
+static void
+update_chassis_transport_zones(const struct sset *transport_zones,
+                               const struct sbrec_chassis *chassis_rec)
+{
+    struct sset chassis_tzones_set = SSET_INITIALIZER(&chassis_tzones_set);
+    for (int i = 0; i < chassis_rec->n_transport_zones; i++) {
+        sset_add(&chassis_tzones_set, chassis_rec->transport_zones[i]);
+    }
+
+    /* Only update the transport zones if something changed */
+    if (!sset_equals(transport_zones, &chassis_tzones_set)) {
+        const char **ls_arr = sset_array(transport_zones);
+        sbrec_chassis_set_transport_zones(chassis_rec, ls_arr,
+                                          sset_count(transport_zones));
+        free(ls_arr);
+    }
+
+    sset_destroy(&chassis_tzones_set);
+}
+
 /* Returns this chassis's Chassis record, if it is available. */
 const struct sbrec_chassis *
 chassis_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
             struct ovsdb_idl_index *sbrec_chassis_by_name,
             const struct ovsrec_open_vswitch_table *ovs_table,
             const char *chassis_id,
-            const struct ovsrec_bridge *br_int)
+            const struct ovsrec_bridge *br_int,
+            const struct sset *transport_zones)
 {
     const struct sbrec_chassis *chassis_rec
         = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
@@ -157,6 +179,8 @@ chassis_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
             sbrec_chassis_set_hostname(chassis_rec, hostname);
         }
 
+        update_chassis_transport_zones(transport_zones, chassis_rec);
+
         /* Determine new values for Chassis external-ids. */
         const char *chassis_bridge_mappings
             = get_bridge_mappings(&chassis_rec->external_ids);
diff --git a/ovn/controller/chassis.h b/ovn/controller/chassis.h
index 6b1c357d2..9847e19f6 100644
--- a/ovn/controller/chassis.h
+++ b/ovn/controller/chassis.h
@@ -25,13 +25,15 @@ struct ovsrec_bridge;
 struct ovsrec_open_vswitch_table;
 struct sbrec_chassis;
 struct sbrec_chassis_table;
+struct sset;
 
 void chassis_register_ovs_idl(struct ovsdb_idl *);
 const struct sbrec_chassis *chassis_run(
     struct ovsdb_idl_txn *ovnsb_idl_txn,
     struct ovsdb_idl_index *sbrec_chassis_by_name,
     const struct ovsrec_open_vswitch_table *,
-    const char *chassis_id, const struct ovsrec_bridge *br_int);
+    const char *chassis_id, const struct ovsrec_bridge *br_int,
+    const struct sset *transport_zones);
 bool chassis_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
                      const struct sbrec_chassis *);
 
diff --git a/ovn/controller/encaps.c b/ovn/controller/encaps.c
index 610b833de..dcf78108d 100644
--- a/ovn/controller/encaps.c
+++ b/ovn/controller/encaps.c
@@ -195,13 +195,36 @@ chassis_tunnel_add(const struct sbrec_chassis *chassis_rec, const struct sbrec_s
     return tuncnt;
 }
 
+/*
+* Returns true if transport_zones and chassis_rec->transport_zones
+* have at least one common transport zone.
+*/
+static bool
+chassis_tzones_overlap(const struct sset *transport_zones,
+                       const struct sbrec_chassis *chassis_rec)
+{
+    /* If neither Chassis belongs to any transport zones, return true to
+     * form a tunnel between them */
+    if (!chassis_rec->n_transport_zones && sset_is_empty(transport_zones)) {
+        return true;
+    }
+
+    for (int i = 0; i < chassis_rec->n_transport_zones; i++) {
+        if (sset_contains(transport_zones, chassis_rec->transport_zones[i])) {
+            return true;
+        }
+    }
+    return false;
+}
+
 void
 encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
            const struct ovsrec_bridge_table *bridge_table,
            const struct ovsrec_bridge *br_int,
            const struct sbrec_chassis_table *chassis_table,
            const char *chassis_id,
-           const struct sbrec_sb_global *sbg)
+           const struct sbrec_sb_global *sbg,
+           const struct sset *transport_zones)
 {
     if (!ovs_idl_txn || !br_int) {
         return;
@@ -251,7 +274,15 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
 
     SBREC_CHASSIS_TABLE_FOR_EACH (chassis_rec, chassis_table) {
         if (strcmp(chassis_rec->name, chassis_id)) {
-            /* Create tunnels to the other chassis. */
+            /* Create tunnels to the other Chassis belonging to the
+             * same transport zone */
+            if (!chassis_tzones_overlap(transport_zones, chassis_rec)) {
+                VLOG_DBG("Skipping encap creation for Chassis '%s' because "
+                         "it belongs to different transport zones",
+                         chassis_rec->name);
+                continue;
+            }
+
             if (chassis_tunnel_add(chassis_rec, sbg, &tc) == 0) {
                 VLOG_INFO("Creating encap for '%s' failed", chassis_rec->name);
                 continue;
diff --git a/ovn/controller/encaps.h b/ovn/controller/encaps.h
index 3e0e110ef..7ed3e0939 100644
--- a/ovn/controller/encaps.h
+++ b/ovn/controller/encaps.h
@@ -25,6 +25,7 @@ struct ovsrec_bridge_table;
 struct sbrec_chassis_table;
 struct sbrec_sb_global;
 struct ovsrec_open_vswitch_table;
+struct sset;
 
 void encaps_register_ovs_idl(struct ovsdb_idl *);
 void encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
@@ -32,7 +33,8 @@ void encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
                 const struct ovsrec_bridge *br_int,
                 const struct sbrec_chassis_table *,
                 const char *chassis_id,
-                const struct sbrec_sb_global *);
+                const struct sbrec_sb_global *,
+                const struct sset *transport_zones);
 
 bool encaps_cleanup(struct ovsdb_idl_txn *ovs_idl_txn,
                     const struct ovsrec_bridge *br_int);
diff --git a/ovn/controller/ovn-controller.8.xml b/ovn/controller/ovn-controller.8.xml
index fd2e10a7a..072ec5820 100644
--- a/ovn/controller/ovn-controller.8.xml
+++ b/ovn/controller/ovn-controller.8.xml
@@ -167,6 +167,15 @@
         specific to this particular chassis. An example would be:
         <code>cms_option1,cms_option2:foo</code>.
       </dd>
+
+      <dt><code>external_ids:ovn-transport-zones</code></dt>
+      <dd>
+        The transport zone(s) that this chassis belongs to. Transport
+        zones is a way to group different chassis so that tunnels are only
+        formed between members of the same group(s). Multiple transport
+        zones may be specified with a comma-separated list. For example:
+        tz1,tz2,tz3.
+      </dd>
     </dl>
 
     <p>
diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c
index cf4907db3..69eeee5dc 100644
--- a/ovn/controller/ovn-controller.c
+++ b/ovn/controller/ovn-controller.c
@@ -512,6 +512,14 @@ get_nb_cfg(const struct sbrec_sb_global_table *sb_global_table)
     return sb ? sb->nb_cfg : 0;
 }
 
+static const char *
+get_transport_zones(const struct ovsrec_open_vswitch_table *ovs_table)
+{
+    const struct ovsrec_open_vswitch *cfg
+        = ovsrec_open_vswitch_table_first(ovs_table);
+    return smap_get_def(&cfg->external_ids, "ovn-transport-zones", "");
+}
+
 static void
 ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
 {
@@ -679,6 +687,11 @@ main(int argc, char *argv[])
              * <datapath-tunnel-key>_<port-tunnel-key> */
             struct sset local_lport_ids = SSET_INITIALIZER(&local_lport_ids);
             struct sset active_tunnels = SSET_INITIALIZER(&active_tunnels);
+            /* Contains the transport zones that this Chassis belongs to */
+            struct sset transport_zones = SSET_INITIALIZER(&transport_zones);
+            sset_from_delimited_string(&transport_zones,
+                get_transport_zones(ovsrec_open_vswitch_table_get(
+                                    ovs_idl_loop.idl)), ",");
 
             const struct ovsrec_bridge *br_int
                 = get_br_int(ovs_idl_txn,
@@ -693,12 +706,13 @@ main(int argc, char *argv[])
                 chassis = chassis_run(
                     ovnsb_idl_txn, sbrec_chassis_by_name,
                     ovsrec_open_vswitch_table_get(ovs_idl_loop.idl),
-                    chassis_id, br_int);
+                    chassis_id, br_int, &transport_zones);
                 encaps_run(
                     ovs_idl_txn,
                     ovsrec_bridge_table_get(ovs_idl_loop.idl), br_int,
                     sbrec_chassis_table_get(ovnsb_idl_loop.idl), chassis_id,
-                    sbrec_sb_global_first(ovnsb_idl_loop.idl));
+                    sbrec_sb_global_first(ovnsb_idl_loop.idl),
+                    &transport_zones);
 
                 if (ofctrl_is_connected()) {
                     /* Calculate the active tunnels only if have an an active
@@ -848,6 +862,7 @@ main(int argc, char *argv[])
             sset_destroy(&local_lports);
             sset_destroy(&local_lport_ids);
             sset_destroy(&active_tunnels);
+            sset_destroy(&transport_zones);
 
             struct local_datapath *cur_node, *next_node;
             HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node,
diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema
index d87a02e7b..e6401c83f 100644
--- a/ovn/ovn-sb.ovsschema
+++ b/ovn/ovn-sb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Southbound",
-    "version": "2.2.0",
-    "cksum": "2100715070 17222",
+    "version": "2.2.1",
+    "cksum": "1575754731 17409",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -37,7 +37,10 @@
                 "nb_cfg": {"type": {"key": "integer"}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
+                             "min": 0, "max": "unlimited"}},
+                "transport_zones" : {"type": {"key": "string",
+                                              "min": 0,
+                                              "max": "unlimited"}}},
             "isRoot": true,
             "indexes": [["name"]]},
         "Encap": {
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index 5c4a852a5..e4ac59f1c 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -293,6 +293,14 @@
       See <code>ovn-controller</code>(8) for more information.
     </column>
 
+    <column name="transport_zones">
+      <code>ovn-controller</code> populates this key with the transport
+      zones configured in the <ref table="Open_vSwitch"
+      column="external_ids:ovn-transport-zones"/> column of the Open_vSwitch
+      database's <ref table="Open_vSwitch" db="Open_vSwitch"/> table.
+      See <code>ovn-controller</code>(8) for more information.
+    </column>
+
     <group title="Common Columns">
       The overall purpose of these columns is described under <code>Common
       Columns</code> at the beginning of this document.
diff --git a/tests/ovn.at b/tests/ovn.at
index b3500e8c9..ad4d23caa 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -13652,3 +13652,154 @@ ovn-nbctl list logical_switch_port
 ovn-nbctl list logical_router_port
 
 AT_CLEANUP
+
+AT_SETUP([ovn -- test transport zones])
+ovn_start
+
+net_add n1
+for i in 1 2 3 4 5; do
+    sim_add hv$i
+    as hv$i
+    ovs-vsctl add-br br-phys
+    ovn_attach n1 br-phys 192.168.$i.1
+done
+
+dnl Assert that each Chassis has a tunnel formed to every other Chassis
+as hv1
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv2-0
+ovn-hv3-0
+ovn-hv4-0
+ovn-hv5-0
+]])
+
+as hv2
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv1-0
+ovn-hv3-0
+ovn-hv4-0
+ovn-hv5-0
+]])
+
+as hv3
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv1-0
+ovn-hv2-0
+ovn-hv4-0
+ovn-hv5-0
+]])
+
+as hv4
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv1-0
+ovn-hv2-0
+ovn-hv3-0
+ovn-hv5-0
+]])
+
+as hv5
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv1-0
+ovn-hv2-0
+ovn-hv3-0
+ovn-hv4-0
+]])
+
+dnl Let's now add some Chassis to different transport zones
+dnl * hv1: Will be part of two transport zones: tz1 and tz2 so it
+dnl   should have tunnels formed between the other two Chassis (hv2 and hv3)
+dnl
+dnl * hv2: Will be part of one transport zone: tz1. It should have a tunnel
+dnl   to hv1 but not to hv3
+dnl
+dnl * hv3: Will be part of one transport zone: tz2. It should have a tunnel
+dnl   to hv1 but not to hv2
+dnl
+dnl * hv4 and hv5: Will not have any TZ set so they will keep the tunnels
+dnl   between themselves and remove the tunnels to other Chassis which now
+dnl   belongs to some TZs
+dnl
+as hv1
+ovs-vsctl set open . external-ids:ovn-transport-zones=tz1,tz2
+
+as hv2
+ovs-vsctl set open . external-ids:ovn-transport-zones=tz1
+
+as hv3
+ovs-vsctl set open . external-ids:ovn-transport-zones=tz2
+
+as hv1
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv2-0
+ovn-hv3-0
+]])
+
+as hv2
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv1-0
+]])
+
+as hv3
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv1-0
+]])
+
+as hv4
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv5-0
+]])
+
+as hv5
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv4-0
+]])
+
+dnl Removing the transport zones should make all Chassis to create
+dnl tunnels between every other Chassis again
+for i in 1 2 3; do
+    as hv$i
+    ovs-vsctl remove open . external-ids ovn-transport-zones
+done
+
+as hv1
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv2-0
+ovn-hv3-0
+ovn-hv4-0
+ovn-hv5-0
+]])
+
+as hv2
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv1-0
+ovn-hv3-0
+ovn-hv4-0
+ovn-hv5-0
+]])
+
+as hv3
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv1-0
+ovn-hv2-0
+ovn-hv4-0
+ovn-hv5-0
+]])
+
+as hv4
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv1-0
+ovn-hv2-0
+ovn-hv3-0
+ovn-hv5-0
+]])
+
+as hv5
+AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
+[[ovn-hv1-0
+ovn-hv2-0
+ovn-hv3-0
+ovn-hv4-0
+]])
+
+OVN_CLEANUP([hv1], [hv2], [hv3])
+AT_CLEANUP
