diff --git a/controller/ofctrl.c b/controller/ofctrl.c
index 81a00c8..819e8cf 100644
--- a/controller/ofctrl.c
+++ b/controller/ofctrl.c
@@ -916,6 +916,57 @@ link_flow_to_sb(struct ovn_desired_flow_table *flow_table,
     ovs_list_insert(&stf->flows, &sfr->flow_list);
 }
 
+static bool
+flow_action_has_conj(const struct ovn_flow *f)
+{
+    const struct ofpact *a = NULL;
+
+    OFPACT_FOR_EACH (a, f->ofpacts, f->ofpacts_len) {
+        if (a->type == OFPACT_CONJUNCTION) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/* Returns true if the actions of 'f1' cannot be merged with the actions of
+ * 'f2'. This is the case when one of the flow contains action "conjunction"
+ * and the other doesn't. The function always populates 'f1_has_conj' and
+ * 'f2_has_conj'.
+ */
+static bool
+flow_actions_conflict(const struct ovn_flow *f1, const struct ovn_flow *f2,
+                      bool *f1_has_conj, bool *f2_has_conj)
+{
+    *f1_has_conj = flow_action_has_conj(f1);
+    *f2_has_conj = flow_action_has_conj(f2);
+
+    if ((*f1_has_conj) && !(*f2_has_conj) && f2->ofpacts_len) {
+        return true;
+    }
+
+    if ((*f2_has_conj) && !(*f1_has_conj) && f1->ofpacts_len) {
+        return true;
+    }
+
+    return false;
+}
+
+static void
+flow_log_actions_conflict(const char *msg, const struct ovn_flow *f1,
+                          const struct ovn_flow *f2)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+
+    char *f1_s = ovn_flow_to_string(f1);
+    char *f2_s = ovn_flow_to_string(f2);
+
+    VLOG_WARN_RL(&rl, "Flow actions conflict: %s, flow-1: %s, flow-2: %s",
+                 msg, f1_s, f2_s);
+    free(f1_s);
+    free(f2_s);
+}
+
 /* Flow table interfaces to the rest of ovn-controller. */
 
 /* Adds a flow to 'desired_flows' with the specified 'match' and 'actions' to
@@ -985,14 +1036,46 @@ ofctrl_add_or_append_flow(struct ovn_desired_flow_table *desired_flows,
     struct desired_flow *existing;
     existing = desired_flow_lookup(desired_flows, &f->flow, NULL);
     if (existing) {
-        /* There's already a flow with this particular match. Append the
-         * action to that flow rather than adding a new flow
+        /* There's already a flow with this particular match. Try to append
+         * the action to that flow rather than adding a new flow.
+         *
+         * If exactly one of the existing or new flow includes a conjunction
+         * action then we can't merge the actions. In that case keep the flow
+         * without conjunction action as this is the least restrictive one.
+         */
+        bool existing_conj = false;
+        bool new_conj = false;
+
+        if (flow_actions_conflict(&existing->flow, &f->flow, &existing_conj,
+                                  &new_conj)) {
+            flow_log_actions_conflict("Cannot merge conj with non-conj action",
+                                      &existing->flow, &f->flow);
+        }
+
+        /* If the existing flow is less restrictive (i.e., no conjunction
+         * action) then keep it installed. Store however the flow with
+         * conjunction action too as it will be installed when the current
+         * one is removed.
          */
+        if (!existing_conj && new_conj) {
+            hmap_insert(&desired_flows->match_flow_table, &f->match_hmap_node,
+                        f->flow.hash);
+            link_flow_to_sb(desired_flows, f, sb_uuid);
+            return;
+        }
+
         uint64_t compound_stub[64 / 8];
         struct ofpbuf compound;
         ofpbuf_use_stub(&compound, compound_stub, sizeof(compound_stub));
-        ofpbuf_put(&compound, existing->flow.ofpacts,
-                   existing->flow.ofpacts_len);
+
+        /* Only merge actions if both flows either have action conjunction
+         * or non-conjunction. Otherwise use the actions in the new flow,
+         * the least restrictive.
+         */
+        if (existing_conj == new_conj) {
+            ofpbuf_put(&compound, existing->flow.ofpacts,
+                       existing->flow.ofpacts_len);
+        }
         ofpbuf_put(&compound, f->flow.ofpacts, f->flow.ofpacts_len);
 
         free(existing->flow.ofpacts);
@@ -1687,6 +1770,21 @@ update_installed_flows_by_compare(struct ovn_desired_flow_table *flow_table,
             /* Copy 'd' from 'flow_table' to installed_flows. */
             i = installed_flow_dup(d);
             hmap_insert(&installed_flows, &i->match_hmap_node, i->flow.hash);
+        } else if (i->desired_flow != d) {
+            /* If a matching installed flow was found but its actions are
+             * conflicting with the desired flow actions, we should chose
+             * to install the least restrictive one (i.e., no conjunctive
+             * action).
+             */
+            bool i_conj = false;
+            bool d_conj = false;
+
+            if (flow_actions_conflict(&i->flow, &d->flow, &i_conj, &d_conj) &&
+                    i_conj && !d_conj) {
+                flow_log_actions_conflict("Use new flow", &i->flow, &d->flow);
+                installed_flow_mod(&i->flow, &d->flow, msgs);
+                ovn_flow_log(&i->flow, "updating installed");
+            }
         }
         link_installed_to_desired(i, d);
     }
@@ -1817,7 +1915,23 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table,
                 installed_flow_mod(&i->flow, &f->flow, msgs);
             } else {
                 /* Adding a new flow that conflicts with an existing installed
-                 * flow, so just add it to the link. */
+                 * flow, so just add it to the link.
+                 *
+                 * However, if the existing installed flow is less restrictive
+                 * (i.e., no conjunctive action), we should chose it over the
+                 * existing installed flow.
+                 */
+                bool i_conj = false;
+                bool f_conj = false;
+
+                if (flow_actions_conflict(&i->flow, &f->flow, &i_conj,
+                                          &f_conj) &&
+                        i_conj && !f_conj) {
+                    flow_log_actions_conflict("Use new flow",
+                                              &i->flow, &f->flow);
+                    ovn_flow_log(&i->flow, "updating installed (tracked)");
+                    installed_flow_mod(&i->flow, &f->flow, msgs);
+                }
                 link_installed_to_desired(i, f);
             }
             /* The track_list_node emptyness is used to check if the node is
diff --git a/tests/ovn.at b/tests/ovn.at
index 4e58722..ec9fe0f 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -13709,6 +13709,176 @@ grep conjunction.*conjunction.*conjunction | wc -l`])
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 
+AT_SETUP([ovn -- Superseeding ACLs with conjunction])
+ovn_start
+
+ovn-nbctl ls-add ls1
+
+ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01"
+
+ovn-nbctl lsp-add ls1 ls1-lp2 \
+-- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02"
+
+net_add n1
+sim_add hv1
+
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=1
+
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
+    set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
+    options:tx_pcap=hv1/vif2-tx.pcap \
+    options:rxq_pcap=hv1/vif2-rx.pcap \
+    ofport-request=2
+
+# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
+#
+# This shell function causes an ip packet to be received on INPORT.
+# The packet's content has Ethernet destination DST and source SRC
+# (each exactly 12 hex digits) and Ethernet type ETHTYPE (4 hex digits).
+# The OUTPORTs (zero or more) list the VIFs on which the packet should
+# be received.  INPORT and the OUTPORTs are specified as logical switch
+# port numbers, e.g. 11 for vif11.
+test_ip() {
+    # This packet has bad checksums but logical L3 routing doesn't check.
+    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5
+    local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}\
+${dst_ip}0035111100080000
+    shift; shift; shift; shift; shift
+    as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+    for outport; do
+        echo $packet >> $outport.expected
+    done
+}
+
+ip_to_hex() {
+    printf "%02x%02x%02x%02x" "$@"
+}
+
+reset_pcap_file() {
+    local iface=$1
+    local pcap_file=$2
+    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
+options:rxq_pcap=dummy-rx.pcap
+    rm -f ${pcap_file}*.pcap
+    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
+options:rxq_pcap=${pcap_file}-rx.pcap
+}
+
+# Add a default deny ACL and an allow ACL for specific IP traffic.
+ovn-nbctl acl-add ls1 to-lport 2 'arp' allow
+ovn-nbctl acl-add ls1 to-lport 1 'ip4' drop
+ovn-nbctl acl-add ls1 to-lport 3 '(ip4.src==10.0.0.1 || ip4.src==10.0.0.2) && (ip4.dst == 10.0.0.3 || ip4.dst == 10.0.0.4)' allow
+ovn-nbctl --wait=hv sync
+
+# Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed.
+for src in `seq 1 2`; do
+    for dst in `seq 3 4`; do
+        sip=`ip_to_hex 10 0 0 $src`
+        dip=`ip_to_hex 10 0 0 $dst`
+
+        test_ip 1 f00000000001 f00000000002 $sip $dip 2
+    done
+done
+
+# Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.5 should be dropped.
+dip=`ip_to_hex 10 0 0 5`
+for src in `seq 1 2`; do
+    sip=`ip_to_hex 10 0 0 $src`
+
+    test_ip 1 f00000000001 f00000000002 $sip $dip
+done
+
+cat 2.expected > expout
+$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+AT_CHECK([cat 2.packets], [0], [expout])
+reset_pcap_file hv1-vif2 hv1/vif2
+rm -f 2.packets
+> 2.expected
+
+# Add a less restrictive allow ACL for src IP 10.0.0.1
+ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow
+ovn-nbctl --wait=hv sync
+
+# Check OVS flows, the less restrictive flows should have been installed.
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | \
+    grep "priority=1003" | awk '{print $7 " " $8}' | sort], [0], [dnl
+priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45)
+priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(2,1/2)
+priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(2,1/2)
+priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45)
+priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(2,2/2)
+])
+
+# Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed.
+for src in `seq 1 2`; do
+    for dst in `seq 3 4`; do
+        sip=`ip_to_hex 10 0 0 $src`
+        dip=`ip_to_hex 10 0 0 $dst`
+
+        test_ip 1 f00000000001 f00000000002 $sip $dip 2
+    done
+done
+
+# Traffic 10.0.0.2 -> 10.0.0.5 should be dropped.
+sip=`ip_to_hex 10 0 0 2`
+dip=`ip_to_hex 10 0 0 5`
+test_ip 1 f00000000001 f00000000002 $sip $dip
+
+# Traffic 10.0.0.1 -> 10.0.0.5 should be allowed.
+sip=`ip_to_hex 10 0 0 1`
+dip=`ip_to_hex 10 0 0 5`
+test_ip 1 f00000000001 f00000000002 $sip $dip 2
+
+cat 2.expected > expout
+$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+AT_CHECK([cat 2.packets], [0], [expout])
+reset_pcap_file hv1-vif2 hv1/vif2
+rm -f 2.packets
+> 2.expected
+
+# Remove the less restrictive allow ACL.
+ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1'
+ovn-nbctl --wait=hv sync
+
+# Check OVS flows, the 10.0.0.1 conjunction should have been reinstalled.
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | \
+    grep "priority=1003" | awk '{print $7 " " $8}' | sort], [0], [dnl
+priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45)
+priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(2,1/2)
+priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(2,1/2)
+priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=conjunction(2,2/2)
+priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(2,2/2)
+])
+
+# Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed.
+for src in `seq 1 2`; do
+    for dst in `seq 3 4`; do
+        sip=`ip_to_hex 10 0 0 $src`
+        dip=`ip_to_hex 10 0 0 $dst`
+
+        test_ip 1 f00000000001 f00000000002 $sip $dip 2
+    done
+done
+
+# Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.5 should be dropped.
+dip=`ip_to_hex 10 0 0 5`
+for src in `seq 1 2`; do
+    sip=`ip_to_hex 10 0 0 $src`
+
+    test_ip 1 f00000000001 f00000000002 $sip $dip
+done
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+
 # 3 hypervisors, one logical switch, 3 logical ports per hypervisor
 AT_SETUP([ovn -- L2 Drop and Allow ACL w/ Stateful ACL])
 ovn_start
