diff --git a/ofproto/ofproto-dpif-trace.c b/ofproto/ofproto-dpif-trace.c
index e8fe0c6c17e9..2110fc713f0d 100644
--- a/ofproto/ofproto-dpif-trace.c
+++ b/ofproto/ofproto-dpif-trace.c
@@ -573,6 +573,74 @@ exit:
     free_ct_states(&next_ct_states);
 }
 
+static void
+explain_slow_path(enum slow_path_reason slow, struct ds *output)
+{
+    ds_put_cstr(output, "\nThis flow is handled by the userspace "
+                "slow path because it:");
+    for (; slow; slow = zero_rightmost_1bit(slow)) {
+        enum slow_path_reason bit = rightmost_1bit(slow);
+        ds_put_format(output, "\n  - %s.",
+                      slow_path_reason_to_explanation(bit));
+    }
+}
+
+/* Copies ODP actions from 'in' (with length 'size') to 'out', dropping
+ * OVS_ACTION_ATTR_OUTPUT along the way. */
+static void
+prune_output_actions(const struct ofpbuf *in, struct ofpbuf *out)
+{
+    const struct nlattr *a;
+    unsigned int left;
+    NL_ATTR_FOR_EACH (a, left, in->data, in->size) {
+        if (a->nla_type == OVS_ACTION_ATTR_CLONE) {
+            struct ofpbuf in_nested;
+            nl_attr_get_nested(a, &in_nested);
+
+            size_t ofs = nl_msg_start_nested(out, OVS_ACTION_ATTR_CLONE);
+            prune_output_actions(&in_nested, out);
+            nl_msg_end_nested(out, ofs);
+        } else if (a->nla_type != OVS_ACTION_ATTR_OUTPUT) {
+            ofpbuf_put(out, a, NLA_ALIGN(a->nla_len));
+        }
+    }
+}
+
+/* Executes all of the datapath actions, except for any OVS_ACTION_ATTR_OUTPUT
+ * actions, in 'actions' on 'packet', which has the given 'flow', on 'dpif'.
+ * The actions have slow path reason 'slow' (if any).  Appends any error
+ * message to 'output'.
+ *
+ * This is mainly useful to execute actions to send a packet to an OpenFlow
+ * controller. */
+static void
+execute_actions_except_outputs(struct dpif *dpif,
+                               const struct dp_packet *packet,
+                               const struct flow *flow,
+                               const struct ofpbuf *actions,
+                               enum slow_path_reason slow,
+                               struct ds *output)
+{
+    struct ofpbuf pruned_actions;
+    ofpbuf_init(&pruned_actions, 0);
+    prune_output_actions(actions, &pruned_actions);
+
+    struct dpif_execute execute = {
+        .actions = pruned_actions.data,
+        .actions_len = pruned_actions.size,
+        .needs_help = (slow & SLOW_ACTION) != 0,
+        .flow = flow,
+        .packet = dp_packet_clone_with_headroom(packet, 2),
+    };
+    int error = dpif_execute(dpif, &execute);
+    if (error) {
+        ds_put_format(output, "\nAction execution failed (%s)\n.",
+                      ovs_strerror(error));
+    }
+    dp_packet_delete(execute.packet);
+    ofpbuf_uninit(&pruned_actions);
+}
+
 static void
 ofproto_trace__(struct ofproto_dpif *ofproto, const struct flow *flow,
                 const struct dp_packet *packet, struct ovs_list *recirc_queue,
@@ -627,23 +695,18 @@ ofproto_trace__(struct ofproto_dpif *ofproto, const struct flow *flow,
         ds_put_format(output,
                       "\nTranslation failed (%s), packet is dropped.\n",
                       xlate_strerror(error));
-    } else if (xout.slow) {
-        enum slow_path_reason slow;
-
-        ds_put_cstr(output, "\nThis flow is handled by the userspace "
-                    "slow path because it:");
-
-        slow = xout.slow;
-        while (slow) {
-            enum slow_path_reason bit = rightmost_1bit(slow);
-
-            ds_put_format(output, "\n  - %s.",
-                          slow_path_reason_to_explanation(bit));
-
-            slow &= ~bit;
+    } else {
+        if (xout.slow) {
+            explain_slow_path(xout.slow, output);
+        }
+        if (packet) {
+            execute_actions_except_outputs(ofproto->backer->dpif, packet,
+                                           &initial_flow, &odp_actions,
+                                           xout.slow, output);
         }
     }
 
+
     xlate_out_uninit(&xout);
     ofpbuf_uninit(&odp_actions);
     oftrace_node_list_destroy(&trace);
diff --git a/ofproto/ofproto-unixctl.man b/ofproto/ofproto-unixctl.man
index dede5f39da84..2db4fe6330f9 100644
--- a/ofproto/ofproto-unixctl.man
+++ b/ofproto/ofproto-unixctl.man
@@ -126,9 +126,9 @@ effects when a packet is specified.  If you want side effects to take
 place, then you must supply a packet.
 .
 .IP
-(Side effects when tracing do not have external consequences.  Even if a
-packet is specified, a trace will not output a packet or generate sFlow,
-NetFlow or controller events.)
+(Output actions are obviously side effects too, but
+the trace commands never execute them, even when one specifies a
+packet.)
 .
 .IP "Incomplete information."
 Most of the time, Open vSwitch can figure out everything about the
