diff --git a/controller/ct-zone.c b/controller/ct-zone.c
index 16452bc2d..8ee575cb5 100644
--- a/controller/ct-zone.c
+++ b/controller/ct-zone.c
@@ -26,12 +26,14 @@ ct_zone_restore(const struct sbrec_datapath_binding_table *dp_table,
                 struct ct_zone_ctx *ctx, const char *name, int zone);
 static void ct_zone_add_pending(struct shash *pending_ct_zones,
                                 enum ct_zone_pending_state state,
-                                int zone, bool add, const char *name);
+                                struct ct_zone *zone, bool add,
+                                const char *name);
 static int ct_zone_get_snat(const struct sbrec_datapath_binding *dp);
 static bool ct_zone_assign_unused(struct ct_zone_ctx *ctx,
                                   const char *zone_name, int *scan_start);
-static bool ct_zone_remove(struct ct_zone_ctx *ctx,
-                           struct simap_node *ct_zone);
+static bool ct_zone_remove(struct ct_zone_ctx *ctx, const char *name);
+static void ct_zone_add(struct ct_zone_ctx *ctx, const char *name,
+                        uint16_t zone, bool set_pending);
 
 void
 ct_zones_restore(struct ct_zone_ctx *ctx,
@@ -47,7 +49,8 @@ ct_zones_restore(struct ct_zone_ctx *ctx,
         struct ct_zone_pending_entry *ctpe = pending_node->data;
 
         if (ctpe->add) {
-            ct_zone_restore(dp_table, ctx, pending_node->name, ctpe->zone);
+            ct_zone_restore(dp_table, ctx, pending_node->name,
+                            ctpe->ct_zone.zone);
         }
     }
 
@@ -91,7 +94,6 @@ void
 ct_zones_update(const struct sset *local_lports,
                 const struct hmap *local_datapaths, struct ct_zone_ctx *ctx)
 {
-    struct simap_node *ct_zone;
     int scan_start = 1;
     const char *user;
     struct sset all_users = SSET_INITIALIZER(&all_users);
@@ -132,12 +134,14 @@ ct_zones_update(const struct sset *local_lports,
     }
 
     /* Delete zones that do not exist in above sset. */
-    SIMAP_FOR_EACH_SAFE (ct_zone, &ctx->current) {
-        if (!sset_contains(&all_users, ct_zone->name)) {
-            ct_zone_remove(ctx, ct_zone);
-        } else if (!simap_find(&req_snat_zones, ct_zone->name)) {
-            bitmap_set1(unreq_snat_zones_map, ct_zone->data);
-            simap_put(&unreq_snat_zones, ct_zone->name, ct_zone->data);
+    struct shash_node *node;
+    SHASH_FOR_EACH_SAFE (node, &ctx->current) {
+        struct ct_zone *ct_zone = node->data;
+        if (!sset_contains(&all_users, node->name)) {
+            ct_zone_remove(ctx, node->name);
+        } else if (!simap_find(&req_snat_zones, node->name)) {
+            bitmap_set1(unreq_snat_zones_map, ct_zone->zone);
+            simap_put(&unreq_snat_zones, node->name, ct_zone->zone);
         }
     }
 
@@ -152,7 +156,7 @@ ct_zones_update(const struct sset *local_lports,
             struct simap_node *unreq_node;
             SIMAP_FOR_EACH_SAFE (unreq_node, &unreq_snat_zones) {
                 if (unreq_node->data == snat_req_node->data) {
-                    simap_find_and_delete(&ctx->current, unreq_node->name);
+                    ct_zone_remove(ctx, unreq_node->name);
                     simap_delete(&unreq_snat_zones, unreq_node);
                 }
             }
@@ -163,26 +167,12 @@ ct_zones_update(const struct sset *local_lports,
             bitmap_set0(unreq_snat_zones_map, snat_req_node->data);
         }
 
-        struct simap_node *node = simap_find(&ctx->current,
-                                             snat_req_node->name);
-        if (node) {
-            if (node->data != snat_req_node->data) {
-                /* Zone request has changed for this node. delete old entry and
-                 * create new one*/
-                ct_zone_add_pending(&ctx->pending, CT_ZONE_OF_QUEUED,
-                                    snat_req_node->data, true,
-                                    snat_req_node->name);
-                bitmap_set0(ctx->bitmap, node->data);
-            }
-            bitmap_set1(ctx->bitmap, snat_req_node->data);
-            node->data = snat_req_node->data;
-        } else {
-            ct_zone_add_pending(&ctx->pending, CT_ZONE_OF_QUEUED,
-                                snat_req_node->data, true,
-                                snat_req_node->name);
-            bitmap_set1(ctx->bitmap, snat_req_node->data);
-            simap_put(&ctx->current, snat_req_node->name, snat_req_node->data);
+        struct ct_zone *ct_zone = shash_find_data(&ctx->current,
+                                                  snat_req_node->name);
+        if (node && ct_zone->zone != snat_req_node->data) {
+            ct_zone_remove(ctx, snat_req_node->name);
         }
+        ct_zone_add(ctx, snat_req_node->name, snat_req_node->data, true);
     }
 
     /* xxx This is wasteful to assign a zone to each port--even if no
@@ -191,7 +181,7 @@ ct_zones_update(const struct sset *local_lports,
     /* Assign a unique zone id for each logical port and two zones
      * to a gateway router. */
     SSET_FOR_EACH (user, &all_users) {
-        if (simap_contains(&ctx->current, user)) {
+        if (shash_find(&ctx->current, user)) {
             continue;
         }
 
@@ -221,7 +211,7 @@ ct_zones_commit(const struct ovsrec_bridge *br_int,
 
         char *user_str = xasprintf("ct-zone-%s", iter->name);
         if (ctzpe->add) {
-            char *zone_str = xasprintf("%"PRId32, ctzpe->zone);
+            char *zone_str = xasprintf("%"PRIu16, ctzpe->ct_zone.zone);
             struct smap_node *node =
                     smap_get_node(&br_int->external_ids, user_str);
             if (!node || strcmp(node->value, zone_str)) {
@@ -280,12 +270,17 @@ ct_zone_handle_dp_update(struct ct_zone_ctx *ctx,
      * or not.  If so, then fall back to full recompute of
      * ct_zone engine. */
     char *snat_dp_zone_key = alloc_nat_zone_key(name, "snat");
-    struct simap_node *simap_node =
-            simap_find(&ctx->current, snat_dp_zone_key);
+    struct shash_node *node = shash_find(&ctx->current, snat_dp_zone_key);
     free(snat_dp_zone_key);
-    if (!simap_node || simap_node->data != req_snat_zone) {
-        /* There is no entry yet or the requested snat zone has changed.
-         * Trigger full recompute of ct_zones engine. */
+
+    if (!node) {
+        return false;
+    }
+
+    struct ct_zone *ct_zone = node->data;
+    if (ct_zone->zone != req_snat_zone) {
+        /* The requested snat zone has changed. Trigger full recompute of
+         * ct_zones engine. */
         return false;
     }
 
@@ -297,17 +292,24 @@ bool
 ct_zone_handle_port_update(struct ct_zone_ctx *ctx, const char *name,
                            bool updated, int *scan_start)
 {
-    struct simap_node *ct_zone = simap_find(&ctx->current, name);
-    if (updated && !ct_zone) {
+    struct shash_node *node = shash_find(&ctx->current, name);
+    if (updated && !node) {
         ct_zone_assign_unused(ctx, name, scan_start);
         return true;
-    } else if (!updated && ct_zone_remove(ctx, ct_zone)) {
+    } else if (!updated && node && ct_zone_remove(ctx, node->name)) {
         return true;
     }
 
     return false;
 }
 
+uint16_t
+ct_zone_find_zone(const struct shash *ct_zones, const char *name)
+{
+    struct ct_zone *ct_zone = shash_find_data(ct_zones, name);
+    return ct_zone ? ct_zone->zone : 0;
+}
+
 
 static bool
 ct_zone_assign_unused(struct ct_zone_ctx *ctx, const char *zone_name,
@@ -322,33 +324,55 @@ ct_zone_assign_unused(struct ct_zone_ctx *ctx, const char *zone_name,
     }
 
     *scan_start = zone + 1;
+    ct_zone_add(ctx, zone_name, zone, true);
 
-    ct_zone_add_pending(&ctx->pending, CT_ZONE_OF_QUEUED,
-                        zone, true, zone_name);
-
-    bitmap_set1(ctx->bitmap, zone);
-    simap_put(&ctx->current, zone_name, zone);
     return true;
 }
 
 static bool
-ct_zone_remove(struct ct_zone_ctx *ctx, struct simap_node *ct_zone)
+ct_zone_remove(struct ct_zone_ctx *ctx, const char *name)
 {
-    if (!ct_zone) {
+    struct shash_node *node = shash_find(&ctx->current, name);
+    if (!node) {
         return false;
     }
 
-    VLOG_DBG("removing ct zone %"PRId32" for '%s'", ct_zone->data,
-             ct_zone->name);
+    struct ct_zone *ct_zone = node->data;
+
+    VLOG_DBG("removing ct zone %"PRIu16" for '%s'", ct_zone->zone, name);
 
     ct_zone_add_pending(&ctx->pending, CT_ZONE_OF_QUEUED,
-                        ct_zone->data, false, ct_zone->name);
-    bitmap_set0(ctx->bitmap, ct_zone->data);
-    simap_delete(&ctx->current, ct_zone);
+                        ct_zone, false, name);
+    bitmap_set0(ctx->bitmap, ct_zone->zone);
+    shash_delete(&ctx->current, node);
+    free(ct_zone);
 
     return true;
 }
 
+static void
+ct_zone_add(struct ct_zone_ctx *ctx, const char *name, uint16_t zone,
+            bool set_pending)
+{
+    VLOG_DBG("assigning ct zone %"PRIu16" for '%s'", zone, name);
+
+    struct ct_zone *ct_zone = shash_find_data(&ctx->current, name);
+    if (!ct_zone) {
+        ct_zone = xmalloc(sizeof *ct_zone);
+        shash_add(&ctx->current, name, ct_zone);
+    }
+
+    *ct_zone = (struct ct_zone) {
+        .zone = zone,
+    };
+
+    if (set_pending) {
+        ct_zone_add_pending(&ctx->pending, CT_ZONE_OF_QUEUED,
+                            ct_zone, true, name);
+    }
+    bitmap_set1(ctx->bitmap, zone);
+}
+
 static int
 ct_zone_get_snat(const struct sbrec_datapath_binding *dp)
 {
@@ -358,27 +382,29 @@ ct_zone_get_snat(const struct sbrec_datapath_binding *dp)
 static void
 ct_zone_add_pending(struct shash *pending_ct_zones,
                     enum ct_zone_pending_state state,
-                    int zone, bool add, const char *name)
+                    struct ct_zone *zone, bool add, const char *name)
 {
-    VLOG_DBG("%s ct zone %"PRId32" for '%s'",
-             add ? "assigning" : "removing", zone, name);
-
-    struct ct_zone_pending_entry *pending = xmalloc(sizeof *pending);
-    *pending = (struct ct_zone_pending_entry) {
-        .state = state,
-        .zone = zone,
-        .add = add,
-    };
-
     /* Its important that we add only one entry for the key 'name'.
      * Replace 'pending' with 'existing' and free up 'existing'.
      * Otherwise, we may end up in a continuous loop of adding
      * and deleting the zone entry in the 'external_ids' of
      * integration bridge.
      */
-    struct ct_zone_pending_entry *existing =
-            shash_replace(pending_ct_zones, name, pending);
-    free(existing);
+    struct ct_zone_pending_entry *entry =
+            shash_find_data(pending_ct_zones, name);
+
+    if (!entry) {
+        entry = xmalloc(sizeof *entry);
+        entry->state = CT_ZONE_STATE_NEW;
+
+        shash_add(pending_ct_zones, name, entry);
+    }
+
+    *entry = (struct ct_zone_pending_entry) {
+        .ct_zone = *zone,
+        .state = MIN(entry->state, state),
+        .add = add,
+    };
 }
 
 /* Replaces a UUID prefix from 'uuid_zone' (if any) with the
@@ -419,19 +445,20 @@ ct_zone_restore(const struct sbrec_datapath_binding_table *dp_table,
         VLOG_DBG("ct zone %"PRId32" replace uuid name '%s' with '%s'",
                  zone, name, new_name);
 
+        struct ct_zone ct_zone = {
+            .zone = zone,
+        };
         /* Make sure we remove the uuid one in the next OvS DB commit without
          * flush. */
         ct_zone_add_pending(&ctx->pending, CT_ZONE_DB_QUEUED,
-                            zone, false, name);
+                            &ct_zone, false, name);
         /* Store the zone in OvS DB with name instead of uuid without flush.
          * */
         ct_zone_add_pending(&ctx->pending, CT_ZONE_DB_QUEUED,
-                            zone, true, new_name);
+                            &ct_zone, true, new_name);
         current_name = new_name;
     }
 
-    simap_put(&ctx->current, current_name, zone);
-    bitmap_set1(ctx->bitmap, zone);
-
+    ct_zone_add(ctx, current_name, zone, false);
     free(new_name);
 }
diff --git a/controller/ct-zone.h b/controller/ct-zone.h
index 889bdf2fc..af68de2d6 100644
--- a/controller/ct-zone.h
+++ b/controller/ct-zone.h
@@ -37,8 +37,12 @@ struct ct_zone_ctx {
     struct shash pending;              /* Pending entries,
                                         * 'struct ct_zone_pending_entry'
                                         * by name. */
-    struct simap current;              /* Current CT zones mapping
-                                        * (zone id by name). */
+    struct shash current;              /* Current CT zones mapping
+                                        * (struct ct_zone by name). */
+};
+
+struct ct_zone {
+    uint16_t zone;
 };
 
 /* States to move through when a new conntrack zone has been allocated. */
@@ -47,10 +51,12 @@ enum ct_zone_pending_state {
     CT_ZONE_OF_SENT,      /* Sent and waiting for confirmation on flush. */
     CT_ZONE_DB_QUEUED,    /* Waiting for DB transaction to open. */
     CT_ZONE_DB_SENT,      /* Sent and waiting for confirmation from DB. */
+    CT_ZONE_STATE_NEW,    /* Indication that the pending state was just
+                           * created. Must remain last in the enum. */
 };
 
 struct ct_zone_pending_entry {
-    int zone;
+    struct ct_zone ct_zone;
     bool add;             /* Is the entry being added? */
     ovs_be32 of_xid;      /* Transaction id for barrier. */
     enum ct_zone_pending_state state;
@@ -70,5 +76,6 @@ bool ct_zone_handle_dp_update(struct ct_zone_ctx *ctx,
                               const struct sbrec_datapath_binding *dp);
 bool ct_zone_handle_port_update(struct ct_zone_ctx *ctx, const char *name,
                                 bool updated, int *scan_start);
+uint16_t ct_zone_find_zone(const struct shash *ct_zones, const char *name);
 
 #endif /* controller/ct-zone.h */
diff --git a/controller/ofctrl.c b/controller/ofctrl.c
index 5e54a2e3f..ba6254f89 100644
--- a/controller/ofctrl.c
+++ b/controller/ofctrl.c
@@ -2715,7 +2715,7 @@ ofctrl_put(struct ovn_desired_flow_table *lflow_table,
     SHASH_FOR_EACH(iter, pending_ct_zones) {
         struct ct_zone_pending_entry *ctzpe = iter->data;
         if (ctzpe->state == CT_ZONE_OF_QUEUED) {
-            add_ct_flush_zone(ctzpe->zone, &msgs);
+            add_ct_flush_zone(ctzpe->ct_zone.zone, &msgs);
             ctzpe->state = CT_ZONE_OF_SENT;
             ctzpe->of_xid = 0;
         }
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 7b4ca441a..5663c09bc 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -2186,7 +2186,7 @@ en_ct_zones_init(struct engine_node *node OVS_UNUSED,
     struct ed_type_ct_zones *data = xzalloc(sizeof *data);
 
     shash_init(&data->ctx.pending);
-    simap_init(&data->ctx.current);
+    shash_init(&data->ctx.current);
 
     return data;
 }
@@ -2203,7 +2203,7 @@ en_ct_zones_cleanup(void *data)
 {
     struct ed_type_ct_zones *ct_zones_data = data;
 
-    simap_destroy(&ct_zones_data->ctx.current);
+    shash_destroy_free_data(&ct_zones_data->ctx.current);
     shash_destroy_free_data(&ct_zones_data->ctx.pending);
 }
 
@@ -2256,7 +2256,7 @@ ct_zones_datapath_binding_handler(struct engine_node *node, void *data)
             return false;
         }
 
-        if (!ct_zone_handle_dp_update(&ct_zones_data->ctx.current, dp)) {
+        if (!ct_zone_handle_dp_update(&ct_zones_data->ctx, dp)) {
             return false;
         }
     }
@@ -4316,7 +4316,7 @@ static void init_physical_ctx(struct engine_node *node,
 
     struct ed_type_ct_zones *ct_zones_data =
         engine_get_input_data("ct_zones", node);
-    struct simap *ct_zones = &ct_zones_data->ctx.current;
+    struct shash *ct_zones = &ct_zones_data->ctx.current;
 
     parse_encap_ips(ovs_table, &p_ctx->n_encap_ips, &p_ctx->encap_ips);
     p_ctx->sbrec_port_binding_by_name = sbrec_port_binding_by_name;
@@ -6004,12 +6004,13 @@ static void
 ct_zone_list(struct unixctl_conn *conn, int argc OVS_UNUSED,
              const char *argv[] OVS_UNUSED, void *ct_zones_)
 {
-    struct simap *ct_zones = ct_zones_;
+    struct shash *ct_zones = ct_zones_;
     struct ds ds = DS_EMPTY_INITIALIZER;
-    struct simap_node *zone;
+    struct shash_node *node;
 
-    SIMAP_FOR_EACH(zone, ct_zones) {
-        ds_put_format(&ds, "%s %d\n", zone->name, zone->data);
+    SHASH_FOR_EACH (node, ct_zones) {
+        struct ct_zone *ct_zone = node->data;
+        ds_put_format(&ds, "%s %d\n", node->name, ct_zone->zone);
     }
 
     unixctl_command_reply(conn, ds_cstr(&ds));
diff --git a/controller/physical.c b/controller/physical.c
index 7ee308694..55d21f59e 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -17,6 +17,7 @@
 #include "binding.h"
 #include "coverage.h"
 #include "byte-order.h"
+#include "ct-zone.h"
 #include "encaps.h"
 #include "flow.h"
 #include "ha-chassis.h"
@@ -212,10 +213,10 @@ get_vtep_port(const struct hmap *local_datapaths, int64_t tunnel_key)
 
 static struct zone_ids
 get_zone_ids(const struct sbrec_port_binding *binding,
-             const struct simap *ct_zones)
+             const struct shash *ct_zones)
 {
     struct zone_ids zone_ids = {
-        .ct = simap_get(ct_zones, binding->logical_port)
+        .ct = ct_zone_find_zone(ct_zones, binding->logical_port)
     };
 
     const char *name = smap_get(&binding->datapath->external_ids, "name");
@@ -228,11 +229,11 @@ get_zone_ids(const struct sbrec_port_binding *binding,
     }
 
     char *dnat = alloc_nat_zone_key(name, "dnat");
-    zone_ids.dnat = simap_get(ct_zones, dnat);
+    zone_ids.dnat = ct_zone_find_zone(ct_zones, dnat);
     free(dnat);
 
     char *snat = alloc_nat_zone_key(name, "snat");
-    zone_ids.snat = simap_get(ct_zones, snat);
+    zone_ids.snat = ct_zone_find_zone(ct_zones, snat);
     free(snat);
 
     return zone_ids;
@@ -631,7 +632,7 @@ put_chassis_mac_conj_id_flow(const struct sbrec_chassis_table *chassis_table,
 }
 
 static void
-put_replace_chassis_mac_flows(const struct simap *ct_zones,
+put_replace_chassis_mac_flows(const struct shash *ct_zones,
                               const struct
                               sbrec_port_binding *localnet_port,
                               const struct hmap *local_datapaths,
@@ -1476,7 +1477,7 @@ enforce_tunneling_for_multichassis_ports(
 static void
 consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                       enum mf_field_id mff_ovn_geneve,
-                      const struct simap *ct_zones,
+                      const struct shash *ct_zones,
                       const struct sset *active_tunnels,
                       const struct hmap *local_datapaths,
                       const struct shash *local_bindings,
@@ -2089,7 +2090,7 @@ mc_ofctrl_add_flow(const struct sbrec_multicast_group *mc,
 static void
 consider_mc_group(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                   enum mf_field_id mff_ovn_geneve,
-                  const struct simap *ct_zones,
+                  const struct shash *ct_zones,
                   const struct hmap *local_datapaths,
                   struct shash *local_bindings,
                   struct simap *patch_ofports,
@@ -2153,7 +2154,7 @@ consider_mc_group(struct ovsdb_idl_index *sbrec_port_binding_by_name,
             continue;
         }
 
-        int zone_id = simap_get(ct_zones, port->logical_port);
+        int zone_id = ct_zone_find_zone(ct_zones, port->logical_port);
         if (zone_id) {
             put_load(zone_id, MFF_LOG_CT_ZONE, 0, 16, &ofpacts);
         }
diff --git a/controller/physical.h b/controller/physical.h
index 7fe8ee3c1..8ec90a3ab 100644
--- a/controller/physical.h
+++ b/controller/physical.h
@@ -61,7 +61,7 @@ struct physical_ctx {
     const struct if_status_mgr *if_mgr;
     struct hmap *local_datapaths;
     struct sset *local_lports;
-    const struct simap *ct_zones;
+    const struct shash *ct_zones;
     enum mf_field_id mff_ovn_geneve;
     struct shash *local_bindings;
     struct simap *patch_ofports;
