diff --git a/NEWS b/NEWS
index 1ddde15f8..d72139e76 100644
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,8 @@ Post-v21.03.0
     'use_parallel_build' to enable it.  It is disabled by default.
   - Support vlan-passthru mode for tag=0 localnet ports.
   - Support custom 802.11ad EthType for localnet ports.
+  - Introduce a new "allow-stateless" ACL verb to always bypass connection
+    tracking. The existing "allow" verb behavior is left intact.
 
 OVN v21.03.0 - 12 Mar 2021
 -------------------------
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 54e88d3fa..08c4ea852 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -419,7 +419,9 @@
       before eventually advancing to ingress table <code>ACLs</code>. If
       special ports such as route ports or localnet ports can't use ct(), a
       priority-110 flow is added to skip over stateful ACLs. IPv6 Neighbor
-      Discovery and MLD traffic also skips stateful ACLs.
+      Discovery and MLD traffic also skips stateful ACLs. For "allow-stateless"
+      ACLs, a flow is added to bypass setting the hint for connection tracker
+      processing.
     </p>
 
     <p>
@@ -614,6 +616,10 @@
         for new connections and <code>reg0[1] = 1; next;</code> for existing
         connections.
       </li>
+      <li>
+        <code>allow-stateless</code> ACLs translate into logical
+        flows with the <code>next;</code> action.
+      </li>
       <li>
         <code>reject</code> ACLs translate into logical
         flows with the
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 94fae5648..4032f1441 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -4974,7 +4974,52 @@ skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
 }
 
 static void
-build_pre_acls(struct ovn_datapath *od, struct hmap *lflows)
+build_stateless_filter(struct ovn_datapath *od,
+                       const struct nbrec_acl *acl,
+                       struct hmap *lflows)
+{
+    if (!strcmp(acl->direction, "from-lport")) {
+        ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_ACL,
+                                acl->priority + OVN_ACL_PRI_OFFSET,
+                                acl->match,
+                                "next;",
+                                &acl->header_);
+    } else {
+        ovn_lflow_add_with_hint(lflows, od, S_SWITCH_OUT_PRE_ACL,
+                                acl->priority + OVN_ACL_PRI_OFFSET,
+                                acl->match,
+                                "next;",
+                                &acl->header_);
+    }
+}
+
+static void
+build_stateless_filters(struct ovn_datapath *od, struct hmap *port_groups,
+                        struct hmap *lflows)
+{
+    for (size_t i = 0; i < od->nbs->n_acls; i++) {
+        const struct nbrec_acl *acl = od->nbs->acls[i];
+        if (!strcmp(acl->action, "allow-stateless")) {
+            build_stateless_filter(od, acl, lflows);
+        }
+    }
+
+    struct ovn_port_group *pg;
+    HMAP_FOR_EACH (pg, key_node, port_groups) {
+        if (ovn_port_group_ls_find(pg, &od->nbs->header_.uuid)) {
+            for (size_t i = 0; i < pg->nb_pg->n_acls; i++) {
+                const struct nbrec_acl *acl = pg->nb_pg->acls[i];
+                if (!strcmp(acl->action, "allow-stateless")) {
+                    build_stateless_filter(od, acl, lflows);
+                }
+            }
+        }
+    }
+}
+
+static void
+build_pre_acls(struct ovn_datapath *od, struct hmap *port_groups,
+               struct hmap *lflows)
 {
     /* Ingress and Egress Pre-ACL Table (Priority 0): Packets are
      * allowed by default. */
@@ -5002,6 +5047,8 @@ build_pre_acls(struct ovn_datapath *od, struct hmap *lflows)
                                      110, lflows);
         }
 
+        build_stateless_filters(od, port_groups, lflows);
+
         /* Ingress and Egress Pre-ACL Table (Priority 110).
          *
          * Not to do conntrack on ND and ICMP destination
@@ -5369,7 +5416,8 @@ build_acl_log(struct ds *actions, const struct nbrec_acl *acl,
     } else if (!strcmp(acl->action, "reject")) {
         ds_put_cstr(actions, "verdict=reject, ");
     } else if (!strcmp(acl->action, "allow")
-        || !strcmp(acl->action, "allow-related")) {
+        || !strcmp(acl->action, "allow-related")
+        || !strcmp(acl->action, "allow-stateless")) {
         ds_put_cstr(actions, "verdict=allow, ");
     }
 
@@ -5428,7 +5476,16 @@ consider_acl(struct hmap *lflows, struct ovn_datapath *od,
     bool ingress = !strcmp(acl->direction, "from-lport") ? true :false;
     enum ovn_stage stage = ingress ? S_SWITCH_IN_ACL : S_SWITCH_OUT_ACL;
 
-    if (!strcmp(acl->action, "allow")
+    if (!strcmp(acl->action, "allow-stateless")) {
+        struct ds actions = DS_EMPTY_INITIALIZER;
+        build_acl_log(&actions, acl, meter_groups);
+        ds_put_cstr(&actions, "next;");
+        ovn_lflow_add_with_hint(lflows, od, stage,
+                                acl->priority + OVN_ACL_PRI_OFFSET,
+                                acl->match, ds_cstr(&actions),
+                                &acl->header_);
+        ds_destroy(&actions);
+    } else if (!strcmp(acl->action, "allow")
         || !strcmp(acl->action, "allow-related")) {
         /* If there are any stateful flows, we must even commit "allow"
          * actions.  This is because, while the initiater's
@@ -6828,7 +6885,7 @@ build_lswitch_lflows_pre_acl_and_acl(struct ovn_datapath *od,
         od->has_stateful_acl = ls_has_stateful_acl(od);
         od->has_lb_vip = ls_has_lb_vip(od);
 
-        build_pre_acls(od, lflows);
+        build_pre_acls(od, port_groups, lflows);
         build_pre_lb(od, lflows, meter_groups, lbs);
         build_pre_stateful(od, lflows);
         build_acl_hints(od, lflows);
diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
index 7953325aa..5ae52b3e8 100644
--- a/northd/ovn_northd.dl
+++ b/northd/ovn_northd.dl
@@ -1824,6 +1824,27 @@ for (&Switch(.ls =ls)) {
          .external_ids     = map_empty())
 }
 
+for (&SwitchACL(.sw = sw@&Switch{.ls = ls}, .acl = &acl, .has_fair_meter = fair_meter)) {
+    if (sw.has_stateful_acl) {
+        if (acl.action == "allow-stateless") {
+            if (acl.direction == "from-lport") {
+                Flow(.logical_datapath = ls._uuid,
+                     .stage            = s_SWITCH_IN_PRE_ACL(),
+                     .priority         = acl.priority + oVN_ACL_PRI_OFFSET(),
+                     .__match          = acl.__match,
+                     .actions          = "next;",
+                     .external_ids     = stage_hint(acl._uuid))
+            } else {
+                Flow(.logical_datapath = ls._uuid,
+                     .stage            = s_SWITCH_OUT_PRE_ACL(),
+                     .priority         = acl.priority + oVN_ACL_PRI_OFFSET(),
+                     .__match          = acl.__match,
+                     .actions          = "next;",
+                     .external_ids     = stage_hint(acl._uuid))
+            }
+        }
+    }
+}
 
 /* If there are any stateful ACL rules in this datapath, we must
  * send all IP packets through the conntrack action, which handles
@@ -2168,6 +2189,9 @@ function build_acl_log(acl: nb::ACL, fair_meter: bool): string =
             "allow-related" -> {
                 strs.push("verdict=allow")
             },
+            "allow-stateless" -> {
+                strs.push("verdict=allow")
+            },
             _ -> ()
         };
         match (acl.meter) {
@@ -2546,6 +2570,13 @@ for (&SwitchACL(.sw = sw@&Switch{.ls = ls}, .acl = &acl, .has_fair_meter = fair_
                  .actions          = "${acl_log}next;",
                  .external_ids     = stage_hint)
         }
+    } else if (acl.action == "allow-stateless") {
+        Flow(.logical_datapath = ls._uuid,
+             .stage            = stage,
+             .priority         = acl.priority + oVN_ACL_PRI_OFFSET(),
+             .__match          = acl.__match,
+             .actions          = "${acl_log}next;",
+             .external_ids     = stage_hint)
     } else if (acl.action == "drop" or acl.action == "reject") {
         /* The implementation of "drop" differs if stateful ACLs are in
          * use for this datapath.  In that case, the actions differ
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index 29019809c..faf619a1c 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Northbound",
-    "version": "5.31.0",
-    "cksum": "2352750632 28701",
+    "version": "5.32.0",
+    "cksum": "204590300 28863",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -221,7 +221,10 @@
                                             "enum": ["set", ["from-lport", "to-lport"]]}}},
                 "match": {"type": "string"},
                 "action": {"type": {"key": {"type": "string",
-                                            "enum": ["set", ["allow", "allow-related", "drop", "reject"]]}}},
+                                            "enum": ["set",
+                                               ["allow", "allow-related",
+                                                "allow-stateless", "drop",
+                                                "reject"]]}}},
                 "log": {"type": "boolean"},
                 "severity": {"type": {"key": {"type": "string",
                                               "enum": ["set",
diff --git a/ovn-nb.xml b/ovn-nb.xml
index feb38b5d3..5386c8fef 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1791,7 +1791,9 @@
       <p>The action to take when the ACL rule matches:</p>
       <ul>
         <li>
-          <code>allow</code>: Forward the packet.
+          <code>allow</code>: Forward the packet. It will also send the
+          packets through connection tracking when
+          <code>allow-related</code> rules exist on the logical switch.
         </li>
 
         <li>
@@ -1799,6 +1801,11 @@
           (e.g. inbound replies to an outbound connection).
         </li>
 
+        <li>
+          <code>allow-stateless</code>: Always forward the packet in stateless
+          manner. May require defining additional rules for inbound replies.
+        </li>
+
         <li>
           <code>drop</code>: Silently drop the packet.
         </li>
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 32afb4fa8..f62526fd3 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -2592,6 +2592,315 @@ sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == <cleared>/' | sort], [0],
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ovn -- ACL allow-stateless omit conntrack - Logical_Switch])
+ovn_start
+
+ovn-nbctl ls-add ls
+ovn-nbctl lsp-add ls lsp1
+ovn-nbctl lsp-set-addresses lsp1 00:00:00:00:00:01
+ovn-nbctl lsp-add ls lsp2
+ovn-nbctl lsp-set-addresses lsp2 00:00:00:00:00:02
+
+for direction in from to; do
+    ovn-nbctl acl-add ls ${direction}-lport 3 "tcp" allow-related
+    ovn-nbctl acl-add ls ${direction}-lport 2 "udp" allow-related
+    ovn-nbctl acl-add ls ${direction}-lport 1 "ip" drop
+done
+ovn-nbctl --wait=sb sync
+
+flow_eth='eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02'
+flow_ip='ip.ttl==64 && ip4.src == 42.42.42.1 && ip4.dst == 66.66.66.66'
+flow_tcp='tcp && tcp.dst == 80'
+flow_udp='udp && udp.dst == 80'
+
+lsp1_inport=$(fetch_column Port_Binding tunnel_key logical_port=lsp1)
+
+# TCP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+ct_next(ct_state=new|trk) {
+    ct_next(ct_state=new|trk) {
+        output("lsp2");
+    };
+};
+])
+
+# UDP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+    ct_next(ct_state=new|trk) {
+        output("lsp2");
+    };
+};
+])
+
+# Allow stateless for TCP.
+for direction in from to; do
+    ovn-nbctl acl-add ls ${direction}-lport 1 tcp allow-stateless
+done
+ovn-nbctl --wait=sb sync
+
+# TCP packets should not go to conntrack anymore.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK_UNQUOTED([ovn-trace --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+output("lsp2");
+])
+
+# UDP packets still go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+    ct_next(ct_state=new|trk) {
+        output("lsp2");
+    };
+};
+])
+
+# Add a load balancer.
+ovn-nbctl lb-add lb-tcp 66.66.66.66:80 42.42.42.2:8080 tcp
+ovn-nbctl lb-add lb-udp 66.66.66.66:80 42.42.42.2:8080 udp
+ovn-nbctl ls-lb-add ls lb-tcp
+ovn-nbctl ls-lb-add ls lb-udp
+
+# Remove stateless for TCP.
+ovn-nbctl acl-del ls
+ovn-nbctl --wait=sb sync
+
+# TCP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+ct_next(ct_state=new|trk) {
+    ct_lb {
+        reg0[[6]] = 0;
+        *** chk_lb_hairpin_reply action not implemented;
+        reg0[[12]] = 0;
+        ct_next(ct_state=new|trk) {
+            output("lsp2");
+        };
+    };
+};
+])
+
+# UDP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+    ct_lb {
+        reg0[[6]] = 0;
+        *** chk_lb_hairpin_reply action not implemented;
+        reg0[[12]] = 0;
+        ct_next(ct_state=new|trk) {
+            output("lsp2");
+        };
+    };
+};
+])
+
+# Allow stateless for TCP.
+for direction in from to; do
+    ovn-nbctl acl-add ls ${direction}-lport 1 tcp allow-stateless
+done
+ovn-nbctl --wait=sb sync
+
+# TCP packets should go to conntrack for load balancing.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+ct_next(ct_state=new|trk) {
+    ct_lb {
+        reg0[[6]] = 0;
+        *** chk_lb_hairpin_reply action not implemented;
+        reg0[[12]] = 0;
+        ct_next(ct_state=new|trk) {
+            output("lsp2");
+        };
+    };
+};
+])
+
+# UDP packets still go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+    ct_lb {
+        reg0[[6]] = 0;
+        *** chk_lb_hairpin_reply action not implemented;
+        reg0[[12]] = 0;
+        ct_next(ct_state=new|trk) {
+            output("lsp2");
+        };
+    };
+};
+])
+
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ovn -- ACL allow-stateless omit conntrack - Port_Group])
+ovn_start
+
+ovn-nbctl ls-add ls
+ovn-nbctl lsp-add ls lsp1
+ovn-nbctl lsp-set-addresses lsp1 00:00:00:00:00:01
+ovn-nbctl lsp-add ls lsp2
+ovn-nbctl lsp-set-addresses lsp2 00:00:00:00:00:02
+
+ovn-nbctl pg-add pg lsp1 lsp2
+
+for direction in from to; do
+    ovn-nbctl acl-add pg ${direction}-lport 3 "tcp" allow-related
+    ovn-nbctl acl-add pg ${direction}-lport 2 "udp" allow-related
+    ovn-nbctl acl-add pg ${direction}-lport 1 "ip" drop
+done
+ovn-nbctl --wait=sb sync
+
+lsp1_inport=$(fetch_column Port_Binding tunnel_key logical_port=lsp1)
+echo $lsp1_inport
+
+flow_eth='eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02'
+flow_ip='ip.ttl==64 && ip4.src == 42.42.42.1 && ip4.dst == 66.66.66.66'
+flow_tcp='tcp && tcp.dst == 80'
+flow_udp='udp && udp.dst == 80'
+
+# TCP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+ct_next(ct_state=new|trk) {
+    ct_next(ct_state=new|trk) {
+        output("lsp2");
+    };
+};
+])
+
+# UDP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+    ct_next(ct_state=new|trk) {
+        output("lsp2");
+    };
+};
+])
+
+# Allow stateless for TCP.
+for direction in from to; do
+    ovn-nbctl acl-add pg ${direction}-lport 1 tcp allow-stateless
+done
+ovn-nbctl --wait=sb sync
+
+# TCP packets should not go to conntrack anymore.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK_UNQUOTED([ovn-trace --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+output("lsp2");
+])
+
+# UDP packets still go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+    ct_next(ct_state=new|trk) {
+        output("lsp2");
+    };
+};
+])
+
+# Add a load balancer.
+ovn-nbctl lb-add lb-tcp 66.66.66.66:80 42.42.42.2:8080 tcp
+ovn-nbctl lb-add lb-udp 66.66.66.66:80 42.42.42.2:8080 udp
+ovn-nbctl ls-lb-add ls lb-tcp
+ovn-nbctl ls-lb-add ls lb-udp
+
+# Remove stateless for TCP.
+ovn-nbctl acl-del pg
+ovn-nbctl --wait=sb sync
+
+# TCP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+ct_next(ct_state=new|trk) {
+    ct_lb {
+        reg0[[6]] = 0;
+        *** chk_lb_hairpin_reply action not implemented;
+        reg0[[12]] = 0;
+        ct_next(ct_state=new|trk) {
+            output("lsp2");
+        };
+    };
+};
+])
+
+# UDP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+    ct_lb {
+        reg0[[6]] = 0;
+        *** chk_lb_hairpin_reply action not implemented;
+        reg0[[12]] = 0;
+        ct_next(ct_state=new|trk) {
+            output("lsp2");
+        };
+    };
+};
+])
+
+# Allow stateless for TCP.
+for direction in from to; do
+    ovn-nbctl acl-add pg ${direction}-lport 1 tcp allow-stateless
+done
+ovn-nbctl --wait=sb sync
+
+# TCP packets should go to conntrack for load balancing.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+ct_next(ct_state=new|trk) {
+    ct_lb {
+        reg0[[6]] = 0;
+        *** chk_lb_hairpin_reply action not implemented;
+        reg0[[12]] = 0;
+        ct_next(ct_state=new|trk) {
+            output("lsp2");
+        };
+    };
+};
+])
+
+# UDP packets still go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x${lsp1_inport},vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+    ct_lb {
+        reg0[[6]] = 0;
+        *** chk_lb_hairpin_reply action not implemented;
+        reg0[[12]] = 0;
+        ct_next(ct_state=new|trk) {
+            output("lsp2");
+        };
+    };
+};
+])
+
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([ovn -- check BFD config propagation to SBDB])
 AT_KEYWORDS([northd-bfd])
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index 042c21002..48fd0b7ee 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -2303,9 +2303,11 @@ nbctl_acl_add(struct ctl_context *ctx)
 
     /* Validate action. */
     if (strcmp(action, "allow") && strcmp(action, "allow-related")
-        && strcmp(action, "drop") && strcmp(action, "reject")) {
+        && strcmp(action, "allow-stateless") && strcmp(action, "drop")
+        && strcmp(action, "reject")) {
         ctl_error(ctx, "%s: action must be one of \"allow\", "
-                  "\"allow-related\", \"drop\", and \"reject\"", action);
+                  "\"allow-related\", \"allow-stateless\", \"drop\", "
+                  "and \"reject\"", action);
         return;
     }
 
